Profile: Support auto translation in channels (#5891)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com> Co-authored-by: Dmitry Kabanov <dmitrykabanovdev@gmail.com>
This commit is contained in:
parent
aea46b1796
commit
9b82953426
@ -94,6 +94,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
freeze_since_date?: number;
|
freeze_since_date?: number;
|
||||||
freeze_until_date?: number;
|
freeze_until_date?: number;
|
||||||
freeze_appeal_url?: string;
|
freeze_appeal_url?: string;
|
||||||
|
channel_autotranslation_level_min?: number;
|
||||||
stars_stargift_resale_amount_max?: number;
|
stars_stargift_resale_amount_max?: number;
|
||||||
stars_stargift_resale_amount_min?: number;
|
stars_stargift_resale_amount_min?: number;
|
||||||
stars_stargift_resale_commission_permille?: number;
|
stars_stargift_resale_commission_permille?: number;
|
||||||
@ -182,6 +183,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload,
|
bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload,
|
||||||
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
|
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
|
||||||
channelRestrictAdsLevelMin: appConfig.channel_restrict_sponsored_level_min,
|
channelRestrictAdsLevelMin: appConfig.channel_restrict_sponsored_level_min,
|
||||||
|
channelAutoTranslationLevelMin: appConfig.channel_autotranslation_level_min,
|
||||||
paidReactionMaxAmount: appConfig.stars_paid_reaction_amount_max,
|
paidReactionMaxAmount: appConfig.stars_paid_reaction_amount_max,
|
||||||
isChannelRevenueWithdrawalEnabled: appConfig.channel_revenue_withdrawal_enabled,
|
isChannelRevenueWithdrawalEnabled: appConfig.channel_revenue_withdrawal_enabled,
|
||||||
isStarsGiftEnabled: appConfig.stars_gifts_enabled,
|
isStarsGiftEnabled: appConfig.stars_gifts_enabled,
|
||||||
|
|||||||
@ -119,6 +119,8 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
hasGeo: channel?.hasGeo,
|
hasGeo: channel?.hasGeo,
|
||||||
subscriptionUntil: channel?.subscriptionUntilDate,
|
subscriptionUntil: channel?.subscriptionUntilDate,
|
||||||
paidMessagesStars: paidMessagesStars?.toJSNumber(),
|
paidMessagesStars: paidMessagesStars?.toJSNumber(),
|
||||||
|
level: channel?.level,
|
||||||
|
hasAutoTranslation: channel?.autotranslation,
|
||||||
|
|
||||||
...buildApiChatPermissions(peerEntity),
|
...buildApiChatPermissions(peerEntity),
|
||||||
...buildApiChatRestrictions(peerEntity),
|
...buildApiChatRestrictions(peerEntity),
|
||||||
|
|||||||
@ -2087,3 +2087,16 @@ export async function fetchSponsoredPeer({ query }: { query: string }) {
|
|||||||
if (!result || result instanceof GramJs.contacts.SponsoredPeersEmpty) return undefined;
|
if (!result || result instanceof GramJs.contacts.SponsoredPeersEmpty) return undefined;
|
||||||
return buildApiSponsoredPeer(result.peers[0]);
|
return buildApiSponsoredPeer(result.peers[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleAutoTranslation({
|
||||||
|
chat, isEnabled,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat; isEnabled: boolean;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.channels.ToggleAutotranslation({
|
||||||
|
channel: buildInputChannel(chat.id, chat.accessHash),
|
||||||
|
enabled: isEnabled,
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -49,6 +49,8 @@ export interface ApiChat {
|
|||||||
isForumAsMessages?: true;
|
isForumAsMessages?: true;
|
||||||
boostLevel?: number;
|
boostLevel?: number;
|
||||||
botVerificationIconId?: string;
|
botVerificationIconId?: string;
|
||||||
|
hasAutoTranslation?: true;
|
||||||
|
level?: number;
|
||||||
|
|
||||||
// Calls
|
// Calls
|
||||||
isCallActive?: boolean;
|
isCallActive?: boolean;
|
||||||
|
|||||||
@ -235,6 +235,7 @@ export interface ApiAppConfig {
|
|||||||
bandwidthPremiumUploadSpeedup?: number;
|
bandwidthPremiumUploadSpeedup?: number;
|
||||||
bandwidthPremiumDownloadSpeedup?: number;
|
bandwidthPremiumDownloadSpeedup?: number;
|
||||||
channelRestrictAdsLevelMin?: number;
|
channelRestrictAdsLevelMin?: number;
|
||||||
|
channelAutoTranslationLevelMin?: number;
|
||||||
paidReactionMaxAmount?: number;
|
paidReactionMaxAmount?: number;
|
||||||
isChannelRevenueWithdrawalEnabled?: boolean;
|
isChannelRevenueWithdrawalEnabled?: boolean;
|
||||||
isStarsGiftEnabled?: boolean;
|
isStarsGiftEnabled?: boolean;
|
||||||
|
|||||||
@ -2008,3 +2008,4 @@
|
|||||||
"ValueGiftSortByNumber" = "Number";
|
"ValueGiftSortByNumber" = "Number";
|
||||||
"ResellGiftsNoFound" = "No gifts found";
|
"ResellGiftsNoFound" = "No gifts found";
|
||||||
"ResellGiftsClearFilters" = "Clear Filters";
|
"ResellGiftsClearFilters" = "Clear Filters";
|
||||||
|
"AutomaticTranslation" = "Automatic Translation";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import {
|
import {
|
||||||
memo, useMemo, useRef, useState,
|
memo, useCallback, useMemo, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -123,8 +123,8 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
language,
|
language,
|
||||||
detectedChatLanguage,
|
detectedChatLanguage,
|
||||||
doNotTranslate,
|
doNotTranslate,
|
||||||
onTopicSearch,
|
|
||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
|
onTopicSearch,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
joinChannel,
|
joinChannel,
|
||||||
@ -239,7 +239,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
handleSearchClick();
|
handleSearchClick();
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTextWithLanguage = useLastCallback((langKey: string, langCode: string) => {
|
const getTextWithLanguage = useCallback((langKey: string, langCode: string) => {
|
||||||
const simplified = langCode.split('-')[0];
|
const simplified = langCode.split('-')[0];
|
||||||
const translationKey = `TranslateLanguage${simplified.toUpperCase()}`;
|
const translationKey = `TranslateLanguage${simplified.toUpperCase()}`;
|
||||||
const name = lang(translationKey);
|
const name = lang(translationKey);
|
||||||
@ -250,7 +250,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
const translatedNames = new Intl.DisplayNames([language], { type: 'language' });
|
const translatedNames = new Intl.DisplayNames([language], { type: 'language' });
|
||||||
const translatedName = translatedNames.of(langCode)!;
|
const translatedName = translatedNames.of(langCode)!;
|
||||||
return lang(`${langKey}Other`, translatedName);
|
return lang(`${langKey}Other`, translatedName);
|
||||||
});
|
}, [language, lang]);
|
||||||
|
|
||||||
const buttonText = useMemo(() => {
|
const buttonText = useMemo(() => {
|
||||||
if (isTranslating) return lang('ShowOriginalButton');
|
if (isTranslating) return lang('ShowOriginalButton');
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import {
|
|||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectBot,
|
selectBot,
|
||||||
|
selectCanTranslateChat,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
selectChatLastMessage,
|
selectChatLastMessage,
|
||||||
@ -52,6 +53,7 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectTopic,
|
selectTopic,
|
||||||
|
selectTranslationLanguage,
|
||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
||||||
@ -92,13 +94,13 @@ type OwnProps = {
|
|||||||
isComments?: boolean;
|
isComments?: boolean;
|
||||||
canPost: boolean;
|
canPost: boolean;
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
onScrollDownToggle: BooleanToVoidFunction;
|
|
||||||
onNotchToggle: BooleanToVoidFunction;
|
|
||||||
withBottomShift?: boolean;
|
withBottomShift?: boolean;
|
||||||
withDefaultBg: boolean;
|
withDefaultBg: boolean;
|
||||||
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
|
||||||
isContactRequirePremium?: boolean;
|
isContactRequirePremium?: boolean;
|
||||||
paidMessagesStars?: number;
|
paidMessagesStars?: number;
|
||||||
|
onScrollDownToggle: BooleanToVoidFunction;
|
||||||
|
onNotchToggle: BooleanToVoidFunction;
|
||||||
|
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -137,6 +139,9 @@ type StateProps = {
|
|||||||
isChatProtected?: boolean;
|
isChatProtected?: boolean;
|
||||||
hasCustomGreeting?: boolean;
|
hasCustomGreeting?: boolean;
|
||||||
isAppConfigLoaded?: boolean;
|
isAppConfigLoaded?: boolean;
|
||||||
|
canTranslate?: boolean;
|
||||||
|
translationLanguage?: string;
|
||||||
|
shouldAutoTranslate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
||||||
@ -196,16 +201,19 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
areAdsEnabled,
|
areAdsEnabled,
|
||||||
channelJoinInfo,
|
channelJoinInfo,
|
||||||
isChatProtected,
|
isChatProtected,
|
||||||
onIntersectPinnedMessage,
|
|
||||||
onScrollDownToggle,
|
|
||||||
onNotchToggle,
|
|
||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
hasCustomGreeting,
|
hasCustomGreeting,
|
||||||
isAppConfigLoaded,
|
isAppConfigLoaded,
|
||||||
|
canTranslate,
|
||||||
|
translationLanguage,
|
||||||
|
shouldAutoTranslate,
|
||||||
|
onIntersectPinnedMessage,
|
||||||
|
onScrollDownToggle,
|
||||||
|
onNotchToggle,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
||||||
loadMessageViews, loadPeerStoriesByIds, loadFactChecks,
|
loadMessageViews, loadPeerStoriesByIds, loadFactChecks, requestChatTranslation,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
@ -268,6 +276,12 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
memoFocusingIdRef.current = focusingId;
|
memoFocusingIdRef.current = focusingId;
|
||||||
}, [focusingId]);
|
}, [focusingId]);
|
||||||
|
|
||||||
|
// Enable auto translation for the chat if it's available
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shouldAutoTranslate || !canTranslate) return;
|
||||||
|
requestChatTranslation({ chatId, toLanguageCode: translationLanguage });
|
||||||
|
}, [shouldAutoTranslate, canTranslate, translationLanguage, chatId]);
|
||||||
|
|
||||||
useNativeCopySelectedMessages(copyMessagesByIds);
|
useNativeCopySelectedMessages(copyMessagesByIds);
|
||||||
|
|
||||||
const messageGroups = useMemo(() => {
|
const messageGroups = useMemo(() => {
|
||||||
@ -806,6 +820,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const hasCustomGreeting = Boolean(userFullInfo?.businessIntro);
|
const hasCustomGreeting = Boolean(userFullInfo?.businessIntro);
|
||||||
const isAppConfigLoaded = global.isAppConfigLoaded;
|
const isAppConfigLoaded = global.isAppConfigLoaded;
|
||||||
|
|
||||||
|
const canTranslate = selectCanTranslateChat(global, chatId) && !chatFullInfo?.isTranslationDisabled;
|
||||||
|
const shouldAutoTranslate = chat?.hasAutoTranslation;
|
||||||
|
const translationLanguage = selectTranslationLanguage(global);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
areAdsEnabled,
|
areAdsEnabled,
|
||||||
isChatLoaded: true,
|
isChatLoaded: true,
|
||||||
@ -842,6 +860,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
hasCustomGreeting,
|
hasCustomGreeting,
|
||||||
isAppConfigLoaded,
|
isAppConfigLoaded,
|
||||||
|
canTranslate,
|
||||||
|
translationLanguage,
|
||||||
|
shouldAutoTranslate,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MessageList));
|
)(MessageList));
|
||||||
|
|||||||
@ -747,7 +747,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const detectedLanguage = useTextLanguage(
|
const detectedLanguage = useTextLanguage(
|
||||||
text?.text,
|
text?.text,
|
||||||
!(areTranslationsEnabled || shouldDetectChatLanguage),
|
!(areTranslationsEnabled && shouldDetectChatLanguage),
|
||||||
getIsMessageListReady,
|
getIsMessageListReady,
|
||||||
);
|
);
|
||||||
useDetectChatLanguage(message, detectedLanguage, !shouldDetectChatLanguage, getIsMessageListReady);
|
useDetectChatLanguage(message, detectedLanguage, !shouldDetectChatLanguage, getIsMessageListReady);
|
||||||
|
|||||||
@ -30,13 +30,25 @@ export default function useDetectChatLanguage(
|
|||||||
if (isDisabled || (getIsReady && !getIsReady())) return;
|
if (isDisabled || (getIsReady && !getIsReady())) return;
|
||||||
|
|
||||||
const isTranslatable = Boolean(message.content.text?.text.length);
|
const isTranslatable = Boolean(message.content.text?.text.length);
|
||||||
processMessageMetadata(message.chatId, message.id, isTranslatable, detectedLanguage);
|
processMessageMetadata({
|
||||||
|
chatId: message.chatId,
|
||||||
|
id: message.id,
|
||||||
|
isTranslatable,
|
||||||
|
detectedLanguage,
|
||||||
|
});
|
||||||
}, [message, detectedLanguage, isDisabled, getIsReady]);
|
}, [message, detectedLanguage, isDisabled, getIsReady]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const throttledMakeChatDecision = throttle(makeChatDecision, THROTTLE_DELAY);
|
const throttledMakeChatDecision = throttle(makeChatDecision, THROTTLE_DELAY);
|
||||||
|
|
||||||
function processMessageMetadata(chatId: string, id: number, isTranslatable: boolean, detectedLanguage?: string) {
|
function processMessageMetadata({
|
||||||
|
chatId, id, isTranslatable, detectedLanguage,
|
||||||
|
}: {
|
||||||
|
chatId: string;
|
||||||
|
id: number;
|
||||||
|
isTranslatable: boolean;
|
||||||
|
detectedLanguage?: string;
|
||||||
|
}) {
|
||||||
const chatStats = CHAT_STATS.get(chatId) || new LimitedMap<number, MessageMetadata>(MESSAGES_LIMIT);
|
const chatStats = CHAT_STATS.get(chatId) || new LimitedMap<number, MessageMetadata>(MESSAGES_LIMIT);
|
||||||
|
|
||||||
const previousMetadata = chatStats.get(id);
|
const previousMetadata = chatStats.get(id);
|
||||||
@ -90,7 +102,7 @@ function makeChatDecision(chatId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const translatableRatio = translatableCount / messagesChecked;
|
const translatableRatio = translatableCount / messagesChecked;
|
||||||
const detectableRatio = detectableCount / messagesChecked;
|
const detectableRatio = detectableCount / translatableCount;
|
||||||
|
|
||||||
if (translatableRatio < MIN_TRANSLATABLE_RATIO || detectableRatio < MIN_DETECTABLE_RATIO) {
|
if (translatableRatio < MIN_TRANSLATABLE_RATIO || detectableRatio < MIN_DETECTABLE_RATIO) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import {
|
import {
|
||||||
memo, useCallback, useEffect, useMemo, useState,
|
memo, useEffect, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -11,12 +11,13 @@ import type {
|
|||||||
import { ApiMediaFormat } from '../../../api/types';
|
import { ApiMediaFormat } from '../../../api/types';
|
||||||
import { ManagementProgress, ManagementScreens } from '../../../types';
|
import { ManagementProgress, ManagementScreens } from '../../../types';
|
||||||
|
|
||||||
import { getChatAvatarHash, getHasAdminRight, isChatPublic } from '../../../global/helpers';
|
import { getChatAvatarHash, getHasAdminRight, isChatChannel, isChatPublic } from '../../../global/helpers';
|
||||||
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
||||||
import { formatInteger } from '../../../util/textFormat';
|
import { formatInteger } from '../../../util/textFormat';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ import FloatingActionButton from '../../ui/FloatingActionButton';
|
|||||||
import InputText from '../../ui/InputText';
|
import InputText from '../../ui/InputText';
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import Spinner from '../../ui/Spinner';
|
import Spinner from '../../ui/Spinner';
|
||||||
|
import Switcher from '../../ui/Switcher';
|
||||||
import TextArea from '../../ui/TextArea';
|
import TextArea from '../../ui/TextArea';
|
||||||
|
|
||||||
import './Management.scss';
|
import './Management.scss';
|
||||||
@ -46,6 +48,8 @@ type StateProps = {
|
|||||||
canInvite?: boolean;
|
canInvite?: boolean;
|
||||||
exportedInvites?: ApiExportedInvite[];
|
exportedInvites?: ApiExportedInvite[];
|
||||||
availableReactions?: ApiAvailableReaction[];
|
availableReactions?: ApiAvailableReaction[];
|
||||||
|
hasAutoTranslation?: boolean;
|
||||||
|
canToggleAutoTranslation?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHANNEL_TITLE_EMPTY = 'Channel title can\'t be empty';
|
const CHANNEL_TITLE_EMPTY = 'Channel title can\'t be empty';
|
||||||
@ -63,6 +67,8 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
availableReactions,
|
availableReactions,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onClose,
|
onClose,
|
||||||
|
hasAutoTranslation,
|
||||||
|
canToggleAutoTranslation,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
updateChat,
|
updateChat,
|
||||||
@ -72,6 +78,7 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
openChat,
|
openChat,
|
||||||
loadExportedChatInvites,
|
loadExportedChatInvites,
|
||||||
loadChatJoinRequests,
|
loadChatJoinRequests,
|
||||||
|
toggleAutoTranslation,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const currentTitle = chat?.title || '';
|
const currentTitle = chat?.title || '';
|
||||||
@ -88,6 +95,12 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
const currentAvatarBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
const currentAvatarBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
|
const hasAutoTranslationAvailable = chat && isChatChannel(chat);
|
||||||
|
|
||||||
|
const handleAutoTranslationChange = useLastCallback(() => {
|
||||||
|
toggleAutoTranslation({ chatId, isEnabled: !hasAutoTranslation });
|
||||||
|
});
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive,
|
isActive,
|
||||||
onBack: onClose,
|
onBack: onClose,
|
||||||
@ -112,46 +125,46 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
}, [chatFullInfo?.adminMembersById]);
|
}, [chatFullInfo?.adminMembersById]);
|
||||||
const removedUsersCount = chatFullInfo?.kickedMembers?.length || 0;
|
const removedUsersCount = chatFullInfo?.kickedMembers?.length || 0;
|
||||||
|
|
||||||
const handleClickEditType = useCallback(() => {
|
const handleClickEditType = useLastCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.ChatPrivacyType);
|
onScreenSelect(ManagementScreens.ChatPrivacyType);
|
||||||
}, [onScreenSelect]);
|
});
|
||||||
|
|
||||||
const handleClickDiscussion = useCallback(() => {
|
const handleClickDiscussion = useLastCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.Discussion);
|
onScreenSelect(ManagementScreens.Discussion);
|
||||||
}, [onScreenSelect]);
|
});
|
||||||
|
|
||||||
const handleClickReactions = useCallback(() => {
|
const handleClickReactions = useLastCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.Reactions);
|
onScreenSelect(ManagementScreens.Reactions);
|
||||||
}, [onScreenSelect]);
|
});
|
||||||
|
|
||||||
const handleClickAdministrators = useCallback(() => {
|
const handleClickAdministrators = useLastCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.ChatAdministrators);
|
onScreenSelect(ManagementScreens.ChatAdministrators);
|
||||||
}, [onScreenSelect]);
|
});
|
||||||
|
|
||||||
const handleClickInvites = useCallback(() => {
|
const handleClickInvites = useLastCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.Invites);
|
onScreenSelect(ManagementScreens.Invites);
|
||||||
}, [onScreenSelect]);
|
});
|
||||||
|
|
||||||
const handleClickRequests = useCallback(() => {
|
const handleClickRequests = useLastCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.JoinRequests);
|
onScreenSelect(ManagementScreens.JoinRequests);
|
||||||
}, [onScreenSelect]);
|
});
|
||||||
|
|
||||||
const handleSetPhoto = useCallback((file: File) => {
|
const handleSetPhoto = useLastCallback((file: File) => {
|
||||||
setPhoto(file);
|
setPhoto(file);
|
||||||
setIsProfileFieldsTouched(true);
|
setIsProfileFieldsTouched(true);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleTitleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
const handleTitleChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setTitle(e.target.value);
|
setTitle(e.target.value);
|
||||||
setIsProfileFieldsTouched(true);
|
setIsProfileFieldsTouched(true);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleAboutChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
const handleAboutChange = useLastCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setAbout(e.target.value);
|
setAbout(e.target.value);
|
||||||
setIsProfileFieldsTouched(true);
|
setIsProfileFieldsTouched(true);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleUpdateChannel = useCallback(() => {
|
const handleUpdateChannel = useLastCallback(() => {
|
||||||
const trimmedTitle = title.trim();
|
const trimmedTitle = title.trim();
|
||||||
const trimmedAbout = about.trim();
|
const trimmedAbout = about.trim();
|
||||||
|
|
||||||
@ -166,17 +179,17 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
about: trimmedAbout,
|
about: trimmedAbout,
|
||||||
photo,
|
photo,
|
||||||
});
|
});
|
||||||
}, [about, chatId, photo, title, updateChat]);
|
});
|
||||||
|
|
||||||
const handleClickSubscribers = useCallback(() => {
|
const handleClickSubscribers = useLastCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.ChannelSubscribers);
|
onScreenSelect(ManagementScreens.ChannelSubscribers);
|
||||||
}, [onScreenSelect]);
|
});
|
||||||
|
|
||||||
const handleRemovedUsersClick = useCallback(() => {
|
const handleRemovedUsersClick = useLastCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.ChannelRemovedUsers);
|
onScreenSelect(ManagementScreens.ChannelRemovedUsers);
|
||||||
}, [onScreenSelect]);
|
});
|
||||||
|
|
||||||
const handleDeleteChannel = useCallback(() => {
|
const handleDeleteChannel = useLastCallback(() => {
|
||||||
if (chat.isCreator) {
|
if (chat.isCreator) {
|
||||||
deleteChannel({ chatId: chat.id });
|
deleteChannel({ chatId: chat.id });
|
||||||
} else {
|
} else {
|
||||||
@ -186,7 +199,7 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
closeDeleteDialog();
|
closeDeleteDialog();
|
||||||
closeManagement();
|
closeManagement();
|
||||||
openChat({ id: undefined });
|
openChat({ id: undefined });
|
||||||
}, [chat.isCreator, chat.id, closeDeleteDialog, closeManagement, leaveChannel, deleteChannel, openChat]);
|
});
|
||||||
|
|
||||||
const chatReactionsDescription = useMemo(() => {
|
const chatReactionsDescription = useMemo(() => {
|
||||||
if (!chatFullInfo?.enabledReactions) {
|
if (!chatFullInfo?.enabledReactions) {
|
||||||
@ -290,6 +303,22 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
{chatReactionsDescription}
|
{chatReactionsDescription}
|
||||||
</span>
|
</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
{hasAutoTranslationAvailable && (
|
||||||
|
<ListItem
|
||||||
|
icon="language"
|
||||||
|
narrow
|
||||||
|
ripple
|
||||||
|
disabled={!canToggleAutoTranslation}
|
||||||
|
onClick={handleAutoTranslationChange}
|
||||||
|
>
|
||||||
|
<span>{lang('AutomaticTranslation')}</span>
|
||||||
|
<Switcher
|
||||||
|
id="auto-translation"
|
||||||
|
label={lang('AutomaticTranslation')}
|
||||||
|
checked={hasAutoTranslation}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<ListItem
|
<ListItem
|
||||||
@ -353,7 +382,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { management } = selectTabState(global);
|
const { management } = selectTabState(global);
|
||||||
const { progress } = management;
|
const { progress } = management;
|
||||||
const { invites } = management.byChatId[chatId] || {};
|
const { invites } = management.byChatId[chatId] || {};
|
||||||
|
const minLevelToToggleAutoTranslation = global.appConfig?.channelAutoTranslationLevelMin;
|
||||||
|
const hasAutoTranslation = chat?.hasAutoTranslation;
|
||||||
|
const chatBoostLevel = chat?.level;
|
||||||
|
const canToggleAutoTranslation = chatBoostLevel && minLevelToToggleAutoTranslation
|
||||||
|
? chatBoostLevel >= minLevelToToggleAutoTranslation : false;
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
chatFullInfo: selectChatFullInfo(global, chatId),
|
chatFullInfo: selectChatFullInfo(global, chatId),
|
||||||
@ -362,6 +395,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canInvite: getHasAdminRight(chat, 'inviteUsers'),
|
canInvite: getHasAdminRight(chat, 'inviteUsers'),
|
||||||
exportedInvites: invites,
|
exportedInvites: invites,
|
||||||
availableReactions: global.reactions.availableReactions,
|
availableReactions: global.reactions.availableReactions,
|
||||||
|
hasAutoTranslation,
|
||||||
|
canToggleAutoTranslation,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(global, { chatId }) => {
|
(global, { chatId }) => {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import renderText from '../../common/helpers/renderText';
|
|||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback.ts';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||||
@ -154,14 +154,14 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
if (isChatPublic(linkedGroup)) {
|
if (isChatPublic(linkedGroup)) {
|
||||||
return renderText(
|
return renderText(
|
||||||
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?`,
|
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat?.title}**?`,
|
||||||
['br', 'simple_markdown'],
|
['br', 'simple_markdown'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderText(
|
return renderText(
|
||||||
// eslint-disable-next-line @stylistic/max-len
|
// eslint-disable-next-line @stylistic/max-len
|
||||||
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?\n\nAnyone from the channel will be able to see messages in this group.`,
|
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat?.title}**?\n\nAnyone from the channel will be able to see messages in this group.`,
|
||||||
['br', 'simple_markdown'],
|
['br', 'simple_markdown'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -169,12 +169,14 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
|
|||||||
function renderLinkedGroup() {
|
function renderLinkedGroup() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ListItem
|
{linkedChat && (
|
||||||
className="chat-item-clickable"
|
<ListItem
|
||||||
inactive
|
className="chat-item-clickable"
|
||||||
>
|
inactive
|
||||||
<GroupChatInfo chatId={linkedChat!.id} />
|
>
|
||||||
</ListItem>
|
<GroupChatInfo chatId={linkedChat.id} />
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
<ListItem
|
<ListItem
|
||||||
icon="delete"
|
icon="delete"
|
||||||
ripple
|
ripple
|
||||||
@ -188,7 +190,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={closeConfirmUnlinkGroupDialog}
|
onClose={closeConfirmUnlinkGroupDialog}
|
||||||
header={renderUnlinkGroupHeader()}
|
header={renderUnlinkGroupHeader()}
|
||||||
textParts={renderText(
|
textParts={renderText(
|
||||||
lang(isChannel ? 'DiscussionUnlinkChannelAlert' : 'DiscussionUnlinkGroupAlert', linkedChat!.title),
|
lang(isChannel ? 'DiscussionUnlinkChannelAlert' : 'DiscussionUnlinkGroupAlert', linkedChat?.title),
|
||||||
['br', 'simple_markdown'],
|
['br', 'simple_markdown'],
|
||||||
)}
|
)}
|
||||||
confirmLabel={lang(isChannel ? 'DiscussionUnlinkGroup' : 'DiscussionUnlinkChannel')}
|
confirmLabel={lang(isChannel ? 'DiscussionUnlinkGroup' : 'DiscussionUnlinkChannel')}
|
||||||
|
|||||||
@ -134,3 +134,7 @@
|
|||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.boostInfo {
|
||||||
|
margin: 0 -1rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -226,8 +226,7 @@ const BoostStatistics = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
className="chat-item-clickable"
|
className={buildClassName(styles.boostInfo, 'chat-item-clickable')}
|
||||||
|
|
||||||
onClick={() => handleBoosterClick(boost.userId)}
|
onClick={() => handleBoosterClick(boost.userId)}
|
||||||
>
|
>
|
||||||
<PrivateChatInfo
|
<PrivateChatInfo
|
||||||
|
|||||||
@ -2984,6 +2984,19 @@ addActionHandler('updatePaidMessagesPrice', async (global, actions, payload): Pr
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('toggleAutoTranslation', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId, isEnabled } = payload;
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) return;
|
||||||
|
|
||||||
|
const result = await callApi('toggleAutoTranslation', { chat, isEnabled });
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateChat(global, chatId, { hasAutoTranslation: isEnabled || undefined });
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('resolveBusinessChatLink', async (global, actions, payload): Promise<void> => {
|
addActionHandler('resolveBusinessChatLink', async (global, actions, payload): Promise<void> => {
|
||||||
const { slug, tabId = getCurrentTabId() } = payload;
|
const { slug, tabId = getCurrentTabId() } = payload;
|
||||||
const result = await callApi('resolveBusinessChatLink', { slug });
|
const result = await callApi('resolveBusinessChatLink', { slug });
|
||||||
|
|||||||
@ -283,6 +283,9 @@ export function selectShouldDetectChatLanguage<T extends GlobalState>(
|
|||||||
) {
|
) {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return false;
|
if (!chat) return false;
|
||||||
|
|
||||||
|
if (chat.hasAutoTranslation) return true;
|
||||||
|
|
||||||
const { canTranslateChats } = global.settings.byKey;
|
const { canTranslateChats } = global.settings.byKey;
|
||||||
|
|
||||||
const isPremium = selectIsCurrentUserPremium(global);
|
const isPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|||||||
@ -1081,6 +1081,10 @@ export interface ActionPayloads {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
paidMessagesStars: number;
|
paidMessagesStars: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
toggleAutoTranslation: {
|
||||||
|
chatId: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
updateChat: {
|
updateChat: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
|||||||
@ -1727,6 +1727,7 @@ channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool =
|
|||||||
channels.getChannelRecommendations#25a71742 flags:# channel:flags.0?InputChannel = messages.Chats;
|
channels.getChannelRecommendations#25a71742 flags:# channel:flags.0?InputChannel = messages.Chats;
|
||||||
channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||||
channels.updatePaidMessagesPrice#fc84653f channel:InputChannel send_paid_messages_stars:long = Updates;
|
channels.updatePaidMessagesPrice#fc84653f channel:InputChannel send_paid_messages_stars:long = Updates;
|
||||||
|
channels.toggleAutotranslation#167fc0a1 channel:InputChannel enabled:Bool = Updates;
|
||||||
bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool;
|
bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool;
|
||||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||||
|
|||||||
@ -280,6 +280,7 @@
|
|||||||
"channels.searchPosts",
|
"channels.searchPosts",
|
||||||
"channels.reportSpam",
|
"channels.reportSpam",
|
||||||
"channels.updatePaidMessagesPrice",
|
"channels.updatePaidMessagesPrice",
|
||||||
|
"channels.toggleAutotranslation",
|
||||||
"bots.getBotRecommendations",
|
"bots.getBotRecommendations",
|
||||||
"bots.canSendMessage",
|
"bots.canSendMessage",
|
||||||
"bots.allowSendMessage",
|
"bots.allowSendMessage",
|
||||||
|
|||||||
10
src/types/language.d.ts
vendored
10
src/types/language.d.ts
vendored
@ -1530,6 +1530,7 @@ export interface LangPair {
|
|||||||
'ValueGiftSortByNumber': undefined;
|
'ValueGiftSortByNumber': undefined;
|
||||||
'ResellGiftsNoFound': undefined;
|
'ResellGiftsNoFound': undefined;
|
||||||
'ResellGiftsClearFilters': undefined;
|
'ResellGiftsClearFilters': undefined;
|
||||||
|
'AutomaticTranslation': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V = LangVariable> {
|
export interface LangPairWithVariables<V = LangVariable> {
|
||||||
@ -1773,6 +1774,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'VoipPeerIncompatible': {
|
'VoipPeerIncompatible': {
|
||||||
'user': V;
|
'user': V;
|
||||||
};
|
};
|
||||||
|
'NewDiscussionChatTitle': {
|
||||||
|
'name': V;
|
||||||
|
};
|
||||||
'LastSeenTodayAt': {
|
'LastSeenTodayAt': {
|
||||||
'time': V;
|
'time': V;
|
||||||
};
|
};
|
||||||
@ -2170,6 +2174,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'from': V;
|
'from': V;
|
||||||
'title': V;
|
'title': V;
|
||||||
};
|
};
|
||||||
|
'ActionCreatedChatYou': {
|
||||||
|
'title': V;
|
||||||
|
};
|
||||||
'ActionPaymentDone': {
|
'ActionPaymentDone': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
'user': V;
|
'user': V;
|
||||||
@ -2488,9 +2495,6 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'ComposerTitleForwardFrom': {
|
'ComposerTitleForwardFrom': {
|
||||||
'users': V;
|
'users': V;
|
||||||
};
|
};
|
||||||
'NewDiscussionChatTitle': {
|
|
||||||
'name': V;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairPlural {
|
export interface LangPairPlural {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user