Remove t from utils and most components (#27274)
* Remove t from utils/utils * Remove t from utils/url * Remove t from utils/constants * Remove t from components/widgets * Remove t from components/copy_button * Remove t from components/searchable_channel_list * Fix mocking of React in some tests
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
9187c772b6
Коммит
0da473b9f8
@@ -8,6 +8,7 @@ import useGetHighestThresholdCloudLimit from './useGetHighestThresholdCloudLimit
|
||||
import type {LimitSummary} from './useGetHighestThresholdCloudLimit';
|
||||
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useMemo: (fn: () => LimitSummary) => fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import useGetMultiplesExceededCloudLimit from './useGetMultiplesExceededCloudLim
|
||||
import type {LimitSummary} from './useGetMultiplesExceededCloudLimit';
|
||||
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useMemo: (fn: () => LimitSummary) => fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -4,18 +4,16 @@
|
||||
import classNames from 'classnames';
|
||||
import React, {useRef, useState} from 'react';
|
||||
import {Tooltip} from 'react-bootstrap';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {FormattedMessage, defineMessages, useIntl} from 'react-intl';
|
||||
|
||||
import OverlayTrigger from 'components/overlay_trigger';
|
||||
|
||||
import Constants from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
import {copyToClipboard} from 'utils/utils';
|
||||
|
||||
type Props = {
|
||||
content: string;
|
||||
beforeCopyText?: string;
|
||||
afterCopyText?: string;
|
||||
isForText?: boolean;
|
||||
placement?: string;
|
||||
className?: string;
|
||||
};
|
||||
@@ -41,26 +39,18 @@ const CopyButton: React.FC<Props> = (props: Props) => {
|
||||
copyToClipboard(props.content);
|
||||
};
|
||||
|
||||
const getId = () => {
|
||||
if (isCopied) {
|
||||
return t('copied.message');
|
||||
}
|
||||
return props.beforeCopyText ? t('copy.text.message') : t('copy.code.message');
|
||||
};
|
||||
|
||||
const getDefaultMessage = () => {
|
||||
if (isCopied) {
|
||||
return props.afterCopyText;
|
||||
}
|
||||
return props.beforeCopyText ?? 'Copy code';
|
||||
};
|
||||
let tooltipMessage;
|
||||
if (isCopied) {
|
||||
tooltipMessage = messages.copied;
|
||||
} else if (props.isForText) {
|
||||
tooltipMessage = messages.copyText;
|
||||
} else {
|
||||
tooltipMessage = messages.copyCode;
|
||||
}
|
||||
|
||||
const tooltip = (
|
||||
<Tooltip id='copyButton'>
|
||||
<FormattedMessage
|
||||
id={getId()}
|
||||
defaultMessage={getDefaultMessage()}
|
||||
/>
|
||||
<FormattedMessage {...tooltipMessage}/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -70,13 +60,13 @@ const CopyButton: React.FC<Props> = (props: Props) => {
|
||||
<OverlayTrigger
|
||||
shouldUpdatePosition={true}
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement={props.placement}
|
||||
placement={props.placement ?? 'top'}
|
||||
overlay={tooltip}
|
||||
>
|
||||
<span
|
||||
className={spanClassName}
|
||||
onClick={copyText}
|
||||
aria-label={intl.formatMessage({id: getId(), defaultMessage: getDefaultMessage()})}
|
||||
aria-label={intl.formatMessage(tooltipMessage)}
|
||||
role='button'
|
||||
>
|
||||
{!isCopied &&
|
||||
@@ -94,9 +84,19 @@ const CopyButton: React.FC<Props> = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
CopyButton.defaultProps = {
|
||||
afterCopyText: 'Copied',
|
||||
placement: 'top',
|
||||
};
|
||||
const messages = defineMessages({
|
||||
copied: {
|
||||
id: 'copied.message',
|
||||
defaultMessage: 'Copied',
|
||||
},
|
||||
copyCode: {
|
||||
id: 'copy.code.message',
|
||||
defaultMessage: 'Copy code',
|
||||
},
|
||||
copyText: {
|
||||
id: 'copy.text.message',
|
||||
defaultMessage: 'Copy text',
|
||||
},
|
||||
});
|
||||
|
||||
export default CopyButton;
|
||||
|
||||
@@ -145,15 +145,11 @@ const FilePreviewModalMainActions: React.FC<Props> = (props: Props) => {
|
||||
</ExternalLink>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
const getBeforeCopyText = () => {
|
||||
const fileType = getFileType(props.fileInfo.extension);
|
||||
return fileType === FileTypes.TEXT ? 'Copy text' : undefined;
|
||||
};
|
||||
|
||||
const copy = (
|
||||
<CopyButton
|
||||
className='file-preview-modal-main-actions__action-item'
|
||||
beforeCopyText={getBeforeCopyText()}
|
||||
isForText={getFileType(props.fileInfo.extension) === FileTypes.TEXT}
|
||||
placement={tooltipPlacement}
|
||||
content={props.content}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import {FormattedMessage, injectIntl, type WrappedComponentProps} from 'react-intl';
|
||||
import {FormattedMessage, defineMessages, injectIntl, type WrappedComponentProps} from 'react-intl';
|
||||
|
||||
import {ArchiveOutlineIcon, CheckIcon, ChevronDownIcon, GlobeIcon, LockOutlineIcon, AccountOutlineIcon, GlobeCheckedIcon} from '@mattermost/compass-icons/components';
|
||||
import type {Channel, ChannelMembership} from '@mattermost/types/channels';
|
||||
@@ -21,7 +21,6 @@ import LoadingWrapper from 'components/widgets/loading/loading_wrapper';
|
||||
|
||||
import {isArchivedChannel} from 'utils/channel_utils';
|
||||
import Constants, {ModalIdentifiers} from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
import {isKeyPressed} from 'utils/keyboard';
|
||||
import * as UserAgent from 'utils/user_agent';
|
||||
import {localizeMessage, localizeAndFormatMessage} from 'utils/utils';
|
||||
@@ -162,8 +161,8 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
||||
) : null;
|
||||
|
||||
const channelPurposeContainerAriaLabel = localizeAndFormatMessage(
|
||||
t('more_channels.channel_purpose'),
|
||||
'Channel Information: Membership Indicator: Joined, Member count {memberCount}, Purpose: {channelPurpose}',
|
||||
messages.channelPurpose.id,
|
||||
messages.channelPurpose.defaultMessage,
|
||||
{memberCount, channelPurpose: channel.purpose || ''},
|
||||
);
|
||||
|
||||
@@ -358,7 +357,7 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
||||
listContent = (
|
||||
<div
|
||||
className='no-channel-message'
|
||||
aria-label={this.state.channelSearchValue.length > 0 ? localizeAndFormatMessage(t('more_channels.noMore'), 'No results for {text}', {text: this.state.channelSearchValue}) : localizeMessage('widgets.channels_input.empty', 'No channels found')
|
||||
aria-label={this.state.channelSearchValue.length > 0 ? localizeAndFormatMessage(messages.noMore.id, messages.noMore.defaultMessage, {text: this.state.channelSearchValue}) : localizeMessage('widgets.channels_input.empty', 'No channels found')
|
||||
}
|
||||
>
|
||||
<MagnifyingGlassSVG/>
|
||||
@@ -547,7 +546,7 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
||||
} else if (channels.length === 1) {
|
||||
channelCountLabel = localizeMessage('more_channels.count_one', '1 Result');
|
||||
} else if (channels.length > 1) {
|
||||
channelCountLabel = localizeAndFormatMessage(t('more_channels.count'), '0 Results', {count: channels.length});
|
||||
channelCountLabel = localizeAndFormatMessage(messages.channelCount.id, messages.channelCount.defaultMessage, {count: channels.length});
|
||||
} else {
|
||||
channelCountLabel = localizeMessage('more_channels.count_zero', '0 Results');
|
||||
}
|
||||
@@ -588,4 +587,19 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
channelCount: {
|
||||
id: 'more_channels.count',
|
||||
defaultMessage: '{count} Results',
|
||||
},
|
||||
channelPurpose: {
|
||||
id: 'more_channels.channel_purpose',
|
||||
defaultMessage: 'Channel Information: Membership Indicator: Joined, Member count {memberCount} , Purpose: {channelPurpose}',
|
||||
},
|
||||
noMore: {
|
||||
id: 'more_channels.noMore',
|
||||
defaultMessage: 'No results for {text}',
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(SearchableChannelList);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import type {Group} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
@@ -458,3 +460,26 @@ export default class AtMentionProvider extends Provider {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
defineMessages({
|
||||
groupDivider: {
|
||||
id: 'suggestion.search.group',
|
||||
defaultMessage: 'Group Mentions',
|
||||
},
|
||||
memberDivider: {
|
||||
id: 'suggestion.mention.members',
|
||||
defaultMessage: 'Channel Members',
|
||||
},
|
||||
moreMembersDivider: {
|
||||
id: 'suggestion.mention.moremembers',
|
||||
defaultMessage: 'Other Members',
|
||||
},
|
||||
nonmemberDivider: {
|
||||
id: 'suggestion.mention.nonmembers',
|
||||
defaultMessage: 'Not in Channel',
|
||||
},
|
||||
specialDivider: {
|
||||
id: 'suggestion.mention.special',
|
||||
defaultMessage: 'Special Mentions',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
|
||||
@@ -260,3 +261,10 @@ export default class ChannelMentionProvider extends Provider {
|
||||
this.lastPrefixWithNoResults = '';
|
||||
}
|
||||
}
|
||||
|
||||
defineMessages({
|
||||
myChannelsDivider: {
|
||||
id: 'suggestion.mention.channels',
|
||||
defaultMessage: 'My Channels',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import type {Store} from 'redux';
|
||||
|
||||
import {DockWindowIcon} from '@mattermost/compass-icons/components';
|
||||
@@ -288,3 +289,10 @@ export default class CommandProvider extends Provider {
|
||||
return matches.findIndex((match) => match.Complete === complete) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
defineMessages({
|
||||
commandsDivider: {
|
||||
id: 'suggestion.commands',
|
||||
defaultMessage: 'Commands',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import type {Emoji} from '@mattermost/types/emojis';
|
||||
|
||||
@@ -11,7 +12,6 @@ import {getEmojiImageUrl, isSystemEmoji} from 'mattermost-redux/utils/emoji_util
|
||||
import {getEmojiMap, getRecentEmojisNames} from 'selectors/emojis';
|
||||
import store from 'stores/redux_store';
|
||||
|
||||
import {Preferences} from 'utils/constants';
|
||||
import {compareEmojis, emojiMatchesSkin} from 'utils/emoji_utils';
|
||||
import * as Emoticons from 'utils/emoticons';
|
||||
|
||||
@@ -29,6 +29,8 @@ type EmojiItem = {
|
||||
type: string;
|
||||
}
|
||||
|
||||
const suggestionTypeEmoji = 'emoji';
|
||||
|
||||
const EmoticonSuggestion = React.forwardRef<HTMLDivElement, SuggestionProps<EmojiItem>>((props, ref) => {
|
||||
const text = props.term;
|
||||
const emoji = props.item.emoji;
|
||||
@@ -131,7 +133,7 @@ export default class EmoticonProvider extends Provider {
|
||||
|
||||
// if the emoji has skin, only add those that match with the user selected skin.
|
||||
if (emojiMatchesSkin(emoji, skintone)) {
|
||||
matchedArray.push({name: alias, emoji, type: Preferences.CATEGORY_EMOJI});
|
||||
matchedArray.push({name: alias, emoji, type: suggestionTypeEmoji});
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -145,7 +147,7 @@ export default class EmoticonProvider extends Provider {
|
||||
|
||||
const matchedArray = recentEmojis.includes(name) ? recentMatched : matched;
|
||||
|
||||
matchedArray.push({name, emoji, type: Preferences.CATEGORY_EMOJI});
|
||||
matchedArray.push({name, emoji, type: suggestionTypeEmoji});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,3 +178,10 @@ export default class EmoticonProvider extends Provider {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
defineMessages({
|
||||
emojisDivider: {
|
||||
id: 'suggestion.emoji',
|
||||
defaultMessage: 'Emoji',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {connect, useSelector} from 'react-redux';
|
||||
|
||||
import type {Channel, ChannelMembership, ChannelType} from '@mattermost/types/channels';
|
||||
@@ -863,3 +864,22 @@ export default class SwitchChannelProvider extends Provider {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
defineMessages({
|
||||
moreChannels: {
|
||||
id: 'suggestion.mention.morechannels',
|
||||
defaultMessage: 'Other Channels',
|
||||
},
|
||||
privateChannelsDivider: {
|
||||
id: 'suggestion.mention.private.channels',
|
||||
defaultMessage: 'Private Channels',
|
||||
},
|
||||
recentChannels: {
|
||||
id: 'suggestion.mention.recent.channels',
|
||||
defaultMessage: 'Recent',
|
||||
},
|
||||
unreadChannels: {
|
||||
id: 'suggestion.mention.unread',
|
||||
defaultMessage: 'Unread',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import {t} from 'utils/i18n';
|
||||
|
||||
export default function CloseIcon(props: React.HTMLAttributes<HTMLSpanElement>) {
|
||||
const {formatMessage} = useIntl();
|
||||
return (
|
||||
@@ -15,7 +13,7 @@ export default function CloseIcon(props: React.HTMLAttributes<HTMLSpanElement>)
|
||||
height='24px'
|
||||
viewBox='0 0 24 24'
|
||||
role='img'
|
||||
aria-label={formatMessage({id: t('generic_icons.close'), defaultMessage: 'Close Icon'})}
|
||||
aria-label={formatMessage({id: 'generic_icons.close', defaultMessage: 'Close Icon'})}
|
||||
>
|
||||
<path
|
||||
fillRule='nonzero'
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import type {RefObject} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
import {FormattedMessage, defineMessages} from 'react-intl';
|
||||
import {components} from 'react-select';
|
||||
import type {ValueType, ActionMeta, InputActionMeta} from 'react-select';
|
||||
import type {Async} from 'react-select/async';
|
||||
@@ -19,7 +20,6 @@ import PrivateChannelIcon from 'components/widgets/icons/lock_icon';
|
||||
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
||||
|
||||
import {Constants} from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
|
||||
import './channels_input.scss';
|
||||
|
||||
@@ -31,22 +31,29 @@ type Props = {
|
||||
value: Channel[];
|
||||
onInputChange: (change: string) => void;
|
||||
inputValue: string;
|
||||
loadingMessageId?: string;
|
||||
loadingMessageDefault?: string;
|
||||
noOptionsMessageId?: string;
|
||||
noOptionsMessageDefault?: string;
|
||||
loadingMessage?: MessageDescriptor;
|
||||
noOptionsMessage?: MessageDescriptor;
|
||||
}
|
||||
|
||||
type State = {
|
||||
options: Channel[];
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
loading: {
|
||||
id: 'widgets.channels_input.loading',
|
||||
defaultMessage: 'Loading',
|
||||
},
|
||||
noOptions: {
|
||||
id: 'widgets.channels_input.empty',
|
||||
defaultMessage: 'No channels found',
|
||||
},
|
||||
});
|
||||
|
||||
export default class ChannelsInput extends React.PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
loadingMessageId: t('widgets.channels_input.loading'),
|
||||
loadingMessageDefault: 'Loading',
|
||||
noOptionsMessageId: t('widgets.channels_input.empty'),
|
||||
noOptionsMessageDefault: 'No channels found',
|
||||
loadingMessage: messages.loading,
|
||||
noOptionsMessage: messages.noOptions,
|
||||
};
|
||||
private selectRef: RefObject<Async<Channel> & {handleInputChange: (newValue: string, actionMeta: InputActionMeta | {action: 'custom'}) => string}>;
|
||||
|
||||
@@ -89,8 +96,7 @@ export default class ChannelsInput extends React.PureComponent<Props, State> {
|
||||
loadingMessage = () => {
|
||||
const text = (
|
||||
<FormattedMessage
|
||||
id={this.props.loadingMessageId}
|
||||
defaultMessage={this.props.loadingMessageDefault}
|
||||
{...this.props.loadingMessage}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -108,8 +114,7 @@ export default class ChannelsInput extends React.PureComponent<Props, State> {
|
||||
<div className='channels-input__option channels-input__option--no-matches'>
|
||||
<Msg {...props}>
|
||||
<FormattedMarkdownMessage
|
||||
id={this.props.noOptionsMessageId}
|
||||
defaultMessage={this.props.noOptionsMessageDefault}
|
||||
{...this.props.noOptionsMessage}
|
||||
values={{text: inputValue}}
|
||||
/>
|
||||
</Msg>
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
import type {PrimitiveType, FormatXMLElementFn} from 'intl-messageformat';
|
||||
import React from 'react';
|
||||
import type {ReactNode} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessage, useIntl} from 'react-intl';
|
||||
|
||||
import type {LimitSummary} from 'components/common/hooks/useGetHighestThresholdCloudLimit';
|
||||
import useOpenPricingModal from 'components/common/hooks/useOpenPricingModal';
|
||||
import NotifyAdminCTA from 'components/notify_admin_cta/notify_admin_cta';
|
||||
|
||||
import {MattermostFeatures, LicenseSkus} from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
import {limitThresholds, asGBString, inK, LimitTypes} from 'utils/limits';
|
||||
|
||||
interface Words {
|
||||
@@ -82,36 +81,50 @@ export default function useWords(highestLimit: LimitSummary | false, isAdminUser
|
||||
|
||||
switch (highestLimit.id) {
|
||||
case LimitTypes.messageHistory: {
|
||||
let id = t('workspace_limits.menu_limit.warn.messages_history');
|
||||
let defaultMessage = 'You’re getting closer to the free {limit} message limit. <a>{callToAction}</a>';
|
||||
let description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.warn.messages_history',
|
||||
defaultMessage: 'You’re getting closer to the free {limit} message limit. <a>{callToAction}</a>',
|
||||
});
|
||||
values.limit = intl.formatNumber(highestLimit.limit);
|
||||
if (usageRatio >= limitThresholds.danger) {
|
||||
if (isAdminUser) {
|
||||
id = t('workspace_limits.menu_limit.critical.messages_history');
|
||||
defaultMessage = 'You’re close to hitting the free {limit} message history limit <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.critical.messages_history',
|
||||
defaultMessage: 'You’re close to hitting the free {limit} message history limit <a>{callToAction}</a>',
|
||||
});
|
||||
} else {
|
||||
id = t('workspace_limits.menu_limit.critical.messages_history_non_admin');
|
||||
defaultMessage = 'You\'re almost at the message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.critical.messages_history_non_admin',
|
||||
defaultMessage: 'You\'re almost at the message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (usageRatio >= limitThresholds.reached) {
|
||||
if (isAdminUser) {
|
||||
id = t('workspace_limits.menu_limit.reached.messages_history');
|
||||
defaultMessage = 'You’ve reached the free message history limit. You can only view up to the last {limit} messages in your history. <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.reached.messages_history',
|
||||
defaultMessage: 'You’ve reached the free message history limit. You can only view up to the last {limit} messages in your history. <a>{callToAction}</a>',
|
||||
});
|
||||
values.limit = inK(highestLimit.limit);
|
||||
} else {
|
||||
id = t('workspace_limits.menu_limit.reached.messages_history_non_admin');
|
||||
defaultMessage = 'You’ve reached your message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.reached.messages_history_non_admin',
|
||||
defaultMessage: 'You’ve reached your message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (usageRatio >= limitThresholds.exceeded) {
|
||||
if (isAdminUser) {
|
||||
id = t('workspace_limits.menu_limit.over.messages_history');
|
||||
defaultMessage = 'You’re over the free message history limit. You can only view up to the last {limit} messages in your history. <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.over.messages_history',
|
||||
defaultMessage: 'You’re over the free message history limit. You can only view up to the last {limit} messages in your history. <a>{callToAction}</a>',
|
||||
});
|
||||
values.limit = inK(highestLimit.limit);
|
||||
} else {
|
||||
id = t('workspace_limits.menu_limit.over.messages_history_non_admin');
|
||||
defaultMessage = 'You\'re over your message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.over.messages_history_non_admin',
|
||||
defaultMessage: 'You\'re over your message limit. Your admin can upgrade your plan for unlimited messages. <a>{callToAction}</a>',
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -120,30 +133,35 @@ export default function useWords(highestLimit: LimitSummary | false, isAdminUser
|
||||
defaultMessage: 'Total messages',
|
||||
}),
|
||||
description: intl.formatMessage<ReactNode>(
|
||||
{
|
||||
id,
|
||||
defaultMessage,
|
||||
},
|
||||
description,
|
||||
values,
|
||||
),
|
||||
status: inK(highestLimit.usage),
|
||||
};
|
||||
}
|
||||
case LimitTypes.fileStorage: {
|
||||
let id = t('workspace_limits.menu_limit.warn.files_storage');
|
||||
let defaultMessage = 'You’re getting closer to the {limit} file storage limit. <a>{callToAction}</a>';
|
||||
let description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.warn.files_storage',
|
||||
defaultMessage: 'You’re getting closer to the {limit} file storage limit. <a>{callToAction}</a>',
|
||||
});
|
||||
values.limit = asGBString(highestLimit.limit, intl.formatNumber);
|
||||
if (usageRatio >= limitThresholds.danger) {
|
||||
id = t('workspace_limits.menu_limit.critical.files_storage');
|
||||
defaultMessage = 'You’re getting closer to the {limit} file storage limit. <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.critical.files_storage',
|
||||
defaultMessage: 'You’re getting closer to the {limit} file storage limit. <a>{callToAction}</a>',
|
||||
});
|
||||
}
|
||||
if (usageRatio >= limitThresholds.reached) {
|
||||
id = t('workspace_limits.menu_limit.reached.files_storage');
|
||||
defaultMessage = 'You’ve reached the {limit} file storage limit. You can only access the most recent {limit} worth of files. <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.reached.files_storage',
|
||||
defaultMessage: 'You’ve reached the {limit} file storage limit. You can only access the most recent {limit} worth of files. <a>{callToAction}</a>',
|
||||
});
|
||||
}
|
||||
if (usageRatio >= limitThresholds.exceeded) {
|
||||
id = t('workspace_limits.menu_limit.over.files_storage');
|
||||
defaultMessage = 'You’re over the {limit} file storage limit. You can only access the most recent {limit} worth of files. <a>{callToAction}</a>';
|
||||
description = defineMessage({
|
||||
id: 'workspace_limits.menu_limit.over.files_storage',
|
||||
defaultMessage: 'You’re over the {limit} file storage limit. You can only access the most recent {limit} worth of files. <a>{callToAction}</a>',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -152,10 +170,7 @@ export default function useWords(highestLimit: LimitSummary | false, isAdminUser
|
||||
defaultMessage: 'File storage limit',
|
||||
}),
|
||||
description: intl.formatMessage<ReactNode>(
|
||||
{
|
||||
id,
|
||||
defaultMessage,
|
||||
},
|
||||
description,
|
||||
values,
|
||||
),
|
||||
status: asGBString(highestLimit.usage, intl.formatNumber),
|
||||
|
||||
@@ -5043,7 +5043,6 @@
|
||||
"suggestion.mention.all": "Notifies everyone in this channel",
|
||||
"suggestion.mention.channel": "Notifies everyone in this channel",
|
||||
"suggestion.mention.channels": "My Channels",
|
||||
"suggestion.mention.groups": "Group Mentions",
|
||||
"suggestion.mention.here": "Notifies everyone online in this channel",
|
||||
"suggestion.mention.members": "Channel Members",
|
||||
"suggestion.mention.morechannels": "Other Channels",
|
||||
@@ -5053,7 +5052,6 @@
|
||||
"suggestion.mention.recent.channels": "Recent",
|
||||
"suggestion.mention.special": "Special Mentions",
|
||||
"suggestion.mention.unread": "Unread",
|
||||
"suggestion.mention.unread.channels": "Unread Channels",
|
||||
"suggestion.private": "Private channels",
|
||||
"suggestion.public": "Public channels",
|
||||
"suggestion.search.direct": "Direct Messages",
|
||||
|
||||
@@ -8,6 +8,7 @@ import solarizedLightCSS from 'highlight.js/styles/base16/solarized-light.css';
|
||||
import githubCSS from 'highlight.js/styles/github.css';
|
||||
import monokaiCSS from 'highlight.js/styles/monokai.css';
|
||||
import keyMirror from 'key-mirror';
|
||||
import {defineMessage, defineMessages} from 'react-intl';
|
||||
|
||||
import {CustomStatusDuration} from '@mattermost/types/users';
|
||||
|
||||
@@ -29,7 +30,6 @@ import monokaiIcon from 'images/themes/code_themes/monokai.png';
|
||||
import solarizedDarkIcon from 'images/themes/code_themes/solarized-dark.png';
|
||||
import solarizedLightIcon from 'images/themes/code_themes/solarized-light.png';
|
||||
import logoWebhook from 'images/webhook_icon.jpg';
|
||||
import {t} from 'utils/i18n';
|
||||
|
||||
export const SettingsTypes = {
|
||||
TYPE_TEXT: 'text' as const,
|
||||
@@ -913,16 +913,52 @@ export const AnnouncementBarTypes = {
|
||||
};
|
||||
|
||||
export const AnnouncementBarMessages = {
|
||||
EMAIL_VERIFICATION_REQUIRED: t('announcement_bar.error.email_verification_required'),
|
||||
EMAIL_VERIFIED: t('announcement_bar.notification.email_verified'),
|
||||
LICENSE_EXPIRED: t('announcement_bar.error.license_expired'),
|
||||
LICENSE_EXPIRING: t('announcement_bar.error.license_expiring'),
|
||||
LICENSE_PAST_GRACE: t('announcement_bar.error.past_grace'),
|
||||
PREVIEW_MODE: t('announcement_bar.error.preview_mode'),
|
||||
WEBSOCKET_PORT_ERROR: t('channel_loader.socketError'),
|
||||
TRIAL_LICENSE_EXPIRING: t('announcement_bar.error.trial_license_expiring'),
|
||||
EMAIL_VERIFICATION_REQUIRED: 'announcement_bar.error.email_verification_required',
|
||||
EMAIL_VERIFIED: 'announcement_bar.notification.email_verified',
|
||||
LICENSE_EXPIRED: 'announcement_bar.error.license_expired',
|
||||
LICENSE_EXPIRING: 'announcement_bar.error.license_expiring',
|
||||
LICENSE_PAST_GRACE: 'announcement_bar.error.past_grace',
|
||||
PREVIEW_MODE: 'announcement_bar.error.preview_mode',
|
||||
WEBSOCKET_PORT_ERROR: 'channel_loader.socketError',
|
||||
TRIAL_LICENSE_EXPIRING: 'announcement_bar.error.trial_license_expiring',
|
||||
};
|
||||
|
||||
// These messages correspond to AnnouncementBarMessages above
|
||||
defineMessages({
|
||||
emailVerificationRequired: {
|
||||
id: 'announcement_bar.error.email_verification_required',
|
||||
defaultMessage: 'Check your email inbox to verify the address.',
|
||||
},
|
||||
emailVerified: {
|
||||
id: 'announcement_bar.notification.email_verified',
|
||||
defaultMessage: 'Email verified',
|
||||
},
|
||||
licenseExpired: {
|
||||
id: 'announcement_bar.error.license_expired',
|
||||
defaultMessage: '{licenseSku} license is expired and some features may be disabled.',
|
||||
},
|
||||
licenseExpiring: {
|
||||
id: 'announcement_bar.error.license_expiring',
|
||||
defaultMessage: '{licenseSku} license expires on {date, date, long}.',
|
||||
},
|
||||
pastGrace: {
|
||||
id: 'announcement_bar.error.past_grace',
|
||||
defaultMessage: '{licenseSku} license is expired and some features may be disabled. Please contact your System Administrator for details.',
|
||||
},
|
||||
previewMode: {
|
||||
id: 'announcement_bar.error.preview_mode',
|
||||
defaultMessage: 'Preview Mode: Email notifications have not been configured.',
|
||||
},
|
||||
socketError: {
|
||||
id: 'channel_loader.socketError',
|
||||
defaultMessage: 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to [check WebSocket port](!https://docs.mattermost.com/install/troubleshooting.html#please-check-connection-mattermost-unreachable-if-issue-persists-ask-administrator-to-check-websocket-port).',
|
||||
},
|
||||
trialLicenseExpiring: {
|
||||
id: 'announcement_bar.error.trial_license_expiring',
|
||||
defaultMessage: 'There are {days} days left on your free trial.',
|
||||
},
|
||||
});
|
||||
|
||||
export const VerifyEmailErrors = {
|
||||
FAILED_EMAIL_VERIFICATION: 'failed_email_verification',
|
||||
FAILED_USER_STATE_GET: 'failed_get_user_state',
|
||||
@@ -2070,43 +2106,25 @@ export const WindowSizes = {
|
||||
|
||||
export const AcceptedProfileImageTypes = ['image/jpeg', 'image/png', 'image/bmp'];
|
||||
|
||||
export const searchHintOptions = [{searchTerm: 'From:', message: {id: t('search_list_option.from'), defaultMessage: 'Messages from a user'}},
|
||||
{searchTerm: 'In:', message: {id: t('search_list_option.in'), defaultMessage: 'Messages in a channel'}},
|
||||
{searchTerm: 'On:', message: {id: t('search_list_option.on'), defaultMessage: 'Messages on a date'}},
|
||||
{searchTerm: 'Before:', message: {id: t('search_list_option.before'), defaultMessage: 'Messages before a date'}},
|
||||
{searchTerm: 'After:', message: {id: t('search_list_option.after'), defaultMessage: 'Messages after a date'}},
|
||||
{searchTerm: '-', message: {id: t('search_list_option.exclude'), defaultMessage: 'Exclude search terms'}, additionalDisplay: '—'},
|
||||
{searchTerm: '""', message: {id: t('search_list_option.phrases'), defaultMessage: 'Messages with phrases'}},
|
||||
export const searchHintOptions = [{searchTerm: 'From:', message: defineMessage({id: 'search_list_option.from', defaultMessage: 'Messages from a user'})},
|
||||
{searchTerm: 'In:', message: defineMessage({id: 'search_list_option.in', defaultMessage: 'Messages in a channel'})},
|
||||
{searchTerm: 'On:', message: defineMessage({id: 'search_list_option.on', defaultMessage: 'Messages on a date'})},
|
||||
{searchTerm: 'Before:', message: defineMessage({id: 'search_list_option.before', defaultMessage: 'Messages before a date'})},
|
||||
{searchTerm: 'After:', message: defineMessage({id: 'search_list_option.after', defaultMessage: 'Messages after a date'})},
|
||||
{searchTerm: '-', message: defineMessage({id: 'search_list_option.exclude', defaultMessage: 'Exclude search terms'}), additionalDisplay: '—'},
|
||||
{searchTerm: '""', message: defineMessage({id: 'search_list_option.phrases', defaultMessage: 'Messages with phrases'})},
|
||||
];
|
||||
|
||||
export const searchFilesHintOptions = [{searchTerm: 'From:', message: {id: t('search_files_list_option.from'), defaultMessage: 'Files from a user'}},
|
||||
{searchTerm: 'In:', message: {id: t('search_files_list_option.in'), defaultMessage: 'Files in a channel'}},
|
||||
{searchTerm: 'On:', message: {id: t('search_files_list_option.on'), defaultMessage: 'Files on a date'}},
|
||||
{searchTerm: 'Before:', message: {id: t('search_files_list_option.before'), defaultMessage: 'Files before a date'}},
|
||||
{searchTerm: 'After:', message: {id: t('search_files_list_option.after'), defaultMessage: 'Files after a date'}},
|
||||
{searchTerm: 'Ext:', message: {id: t('search_files_list_option.ext'), defaultMessage: 'Files with a extension'}},
|
||||
{searchTerm: '-', message: {id: t('search_files_list_option.exclude'), defaultMessage: 'Exclude search terms'}, additionalDisplay: '—'},
|
||||
{searchTerm: '""', message: {id: t('search_files_list_option.phrases'), defaultMessage: 'Files with phrases'}},
|
||||
export const searchFilesHintOptions = [{searchTerm: 'From:', message: defineMessage({id: 'search_files_list_option.from', defaultMessage: 'Files from a user'})},
|
||||
{searchTerm: 'In:', message: defineMessage({id: 'search_files_list_option.in', defaultMessage: 'Files in a channel'})},
|
||||
{searchTerm: 'On:', message: defineMessage({id: 'search_files_list_option.on', defaultMessage: 'Files on a date'})},
|
||||
{searchTerm: 'Before:', message: defineMessage({id: 'search_files_list_option.before', defaultMessage: 'Files before a date'})},
|
||||
{searchTerm: 'After:', message: defineMessage({id: 'search_files_list_option.after', defaultMessage: 'Files after a date'})},
|
||||
{searchTerm: 'Ext:', message: defineMessage({id: 'search_files_list_option.ext', defaultMessage: 'Files with a extension'})},
|
||||
{searchTerm: '-', message: defineMessage({id: 'search_files_list_option.exclude', defaultMessage: 'Exclude search terms'}), additionalDisplay: '—'},
|
||||
{searchTerm: '""', message: defineMessage({id: 'search_files_list_option.phrases', defaultMessage: 'Files with phrases'})},
|
||||
];
|
||||
|
||||
// adding these rtranslations here so the weblate CI step will not fail with empty translation strings
|
||||
t('suggestion.archive');
|
||||
t('suggestion.mention.channels');
|
||||
t('suggestion.mention.morechannels');
|
||||
t('suggestion.mention.unread.channels');
|
||||
t('suggestion.mention.unread');
|
||||
t('suggestion.mention.members');
|
||||
t('suggestion.mention.moremembers');
|
||||
t('suggestion.mention.nonmembers');
|
||||
t('suggestion.mention.private.channels');
|
||||
t('suggestion.mention.recent.channels');
|
||||
t('suggestion.mention.special');
|
||||
t('suggestion.mention.groups');
|
||||
t('suggestion.search.public');
|
||||
t('suggestion.search.group');
|
||||
t('suggestion.commands');
|
||||
t('suggestion.emoji');
|
||||
|
||||
const {
|
||||
DONT_CLEAR,
|
||||
THIRTY_MINUTES,
|
||||
@@ -2118,40 +2136,41 @@ const {
|
||||
CUSTOM_DATE_TIME,
|
||||
} = CustomStatusDuration;
|
||||
|
||||
export const durationValues = {
|
||||
export const durationValues = defineMessages({
|
||||
[DONT_CLEAR]: {
|
||||
id: t('custom_status.expiry_dropdown.dont_clear'),
|
||||
id: 'custom_status.expiry_dropdown.dont_clear',
|
||||
defaultMessage: "Don't clear",
|
||||
},
|
||||
[THIRTY_MINUTES]: {
|
||||
id: t('custom_status.expiry_dropdown.thirty_minutes'),
|
||||
id: 'custom_status.expiry_dropdown.thirty_minutes',
|
||||
defaultMessage: '30 minutes',
|
||||
},
|
||||
[ONE_HOUR]: {
|
||||
id: t('custom_status.expiry_dropdown.one_hour'),
|
||||
id: 'custom_status.expiry_dropdown.one_hour',
|
||||
defaultMessage: '1 hour',
|
||||
},
|
||||
[FOUR_HOURS]: {
|
||||
id: t('custom_status.expiry_dropdown.four_hours'),
|
||||
id: 'custom_status.expiry_dropdown.four_hours',
|
||||
defaultMessage: '4 hours',
|
||||
},
|
||||
[TODAY]: {
|
||||
id: t('custom_status.expiry_dropdown.today'),
|
||||
id: 'custom_status.expiry_dropdown.today',
|
||||
defaultMessage: 'Today',
|
||||
},
|
||||
[THIS_WEEK]: {
|
||||
id: t('custom_status.expiry_dropdown.this_week'),
|
||||
id: 'custom_status.expiry_dropdown.this_week',
|
||||
defaultMessage: 'This week',
|
||||
},
|
||||
[DATE_AND_TIME]: {
|
||||
id: t('custom_status.expiry_dropdown.date_and_time'),
|
||||
id: 'custom_status.expiry_dropdown.date_and_time',
|
||||
defaultMessage: 'Custom Date and Time',
|
||||
},
|
||||
[CUSTOM_DATE_TIME]: {
|
||||
id: t('custom_status.expiry_dropdown.date_and_time'),
|
||||
id: 'custom_status.expiry_dropdown.date_and_time',
|
||||
defaultMessage: 'Custom Date and Time',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export enum ClaimErrors {
|
||||
MFA_VALIDATE_TOKEN_AUTHENTICATE = 'mfa.validate_token.authenticate.app_error',
|
||||
ENT_LDAP_LOGIN_USER_NOT_REGISTERED = 'ent.ldap.do_login.user_not_registered.app_error',
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
import {FormattedMessage, defineMessage} from 'react-intl';
|
||||
import type {IntlShape, MessageDescriptor} from 'react-intl';
|
||||
|
||||
import {getModule} from 'module_registry';
|
||||
import Constants from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
import {latinise} from 'utils/latinise';
|
||||
import * as TextFormatting from 'utils/text_formatting';
|
||||
|
||||
@@ -106,18 +105,19 @@ export function getScheme(url: string): string | null {
|
||||
return match && match[1];
|
||||
}
|
||||
|
||||
function formattedError(id: string, message: string, intl?: IntlShape): React.ReactElement | string {
|
||||
function formattedError(message: MessageDescriptor, intl?: IntlShape): React.ReactElement | string {
|
||||
if (intl) {
|
||||
return intl.formatMessage({id, defaultMessage: message});
|
||||
return intl.formatMessage(message);
|
||||
}
|
||||
|
||||
return (<span key={id}>
|
||||
<FormattedMessage
|
||||
id={id}
|
||||
defaultMessage={message}
|
||||
/>
|
||||
<br/>
|
||||
</span>);
|
||||
return (
|
||||
<span key={message.id}>
|
||||
<FormattedMessage
|
||||
{...message}
|
||||
/>
|
||||
<br/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function validateChannelUrl(url: string, intl?: IntlShape): Array<React.ReactElement | string> {
|
||||
@@ -134,34 +134,82 @@ export function validateChannelUrl(url: string, intl?: IntlShape): Array<React.R
|
||||
|
||||
if (cleanedURL !== url || !urlMatched || urlMatched[0] !== url || isDirectMessageFormat || urlLonger || urlShorter) {
|
||||
if (urlLonger) {
|
||||
errors.push(formattedError(t('change_url.longer'), 'URLs must have at least 2 characters.', intl));
|
||||
errors.push(formattedError(
|
||||
defineMessage({
|
||||
id: 'change_url.longer',
|
||||
defaultMessage: 'URLs must have at least 2 characters.',
|
||||
}),
|
||||
intl,
|
||||
));
|
||||
}
|
||||
|
||||
if (urlShorter) {
|
||||
errors.push(formattedError(t('change_url.shorter'), 'URLs must have maximum 64 characters.', intl));
|
||||
errors.push(formattedError(
|
||||
defineMessage({
|
||||
id: 'change_url.shorter',
|
||||
defaultMessage: 'URLs must have maximum 64 characters.',
|
||||
}),
|
||||
intl,
|
||||
));
|
||||
}
|
||||
|
||||
if (url.match(/[^A-Za-z0-9-_]/)) {
|
||||
errors.push(formattedError(t('change_url.noSpecialChars'), 'URLs cannot use special characters.', intl));
|
||||
errors.push(formattedError(
|
||||
defineMessage({
|
||||
id: 'change_url.noSpecialChars',
|
||||
defaultMessage: 'URLs cannot use special characters.',
|
||||
}),
|
||||
intl,
|
||||
));
|
||||
}
|
||||
|
||||
if (isDirectMessageFormat) {
|
||||
errors.push(formattedError(t('change_url.invalidDirectMessage'), 'User IDs are not allowed in channel URLs.', intl));
|
||||
errors.push(formattedError(
|
||||
defineMessage({
|
||||
id: 'change_url.invalidDirectMessage',
|
||||
defaultMessage: 'User IDs are not allowed in channel URLs.',
|
||||
}),
|
||||
intl,
|
||||
));
|
||||
}
|
||||
|
||||
const startsWithoutLetter = url.charAt(0) === '-' || url.charAt(0) === '_';
|
||||
const endsWithoutLetter = url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_');
|
||||
if (startsWithoutLetter && endsWithoutLetter) {
|
||||
errors.push(formattedError(t('change_url.startAndEndWithLetter'), 'URLs must start and end with a lowercase letter or number.', intl));
|
||||
errors.push(formattedError(
|
||||
defineMessage({
|
||||
id: 'change_url.startAndEndWithLetter',
|
||||
defaultMessage: 'URLs must start and end with a lowercase letter or number.',
|
||||
}),
|
||||
intl,
|
||||
));
|
||||
} else if (startsWithoutLetter) {
|
||||
errors.push(formattedError(t('change_url.startWithLetter'), 'URLs must start with a lowercase letter or number.', intl));
|
||||
errors.push(formattedError(
|
||||
defineMessage({
|
||||
id: 'change_url.startWithLetter',
|
||||
defaultMessage: 'URLs must start with a lowercase letter or number.',
|
||||
}),
|
||||
intl,
|
||||
));
|
||||
} else if (endsWithoutLetter) {
|
||||
errors.push(formattedError(t('change_url.endWithLetter'), 'URLs must end with a lowercase letter or number.', intl));
|
||||
errors.push(formattedError(
|
||||
defineMessage({
|
||||
id: 'change_url.endWithLetter',
|
||||
defaultMessage: 'URLs must end with a lowercase letter or number.',
|
||||
}),
|
||||
intl,
|
||||
));
|
||||
}
|
||||
|
||||
// In case of error we don't detect
|
||||
if (errors.length === 0) {
|
||||
errors.push(formattedError(t('change_url.invalidUrl'), 'Invalid URL', intl));
|
||||
errors.push(formattedError(
|
||||
defineMessage({
|
||||
id: 'change_url.invalidUrl',
|
||||
defaultMessage: 'Invalid URL',
|
||||
}),
|
||||
intl,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ import type {TextboxElement} from 'components/textbox';
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
import Constants, {FileTypes, ValidationErrors, A11yCustomEventTypes} from 'utils/constants';
|
||||
import type {A11yFocusEventDetail} from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
import * as Keyboard from 'utils/keyboard';
|
||||
import * as UserAgent from 'utils/user_agent';
|
||||
|
||||
@@ -1236,7 +1235,8 @@ export function getPasswordConfig(config: Partial<ClientConfig>) {
|
||||
}
|
||||
|
||||
export function isValidPassword(password: string, passwordConfig: ReturnType<typeof getPasswordConfig>, intl?: IntlShape) {
|
||||
let errorId = t('user.settings.security.passwordError');
|
||||
// The translation strings used by this function are defined in admin_console/password_settings
|
||||
let errorId = 'user.settings.security.passwordError';
|
||||
const telemetryErrorIds = [];
|
||||
let valid = true;
|
||||
const minimumLength = passwordConfig.minimumLength || Constants.MIN_PASSWORD_LENGTH;
|
||||
|
||||
Ссылка в новой задаче
Block a user