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:
Alexander Zinchuk 2025-06-18 17:40:58 +02:00
parent aea46b1796
commit 9b82953426
21 changed files with 184 additions and 64 deletions

View File

@ -94,6 +94,7 @@ export interface GramJsAppConfig extends LimitsConfig {
freeze_since_date?: number;
freeze_until_date?: number;
freeze_appeal_url?: string;
channel_autotranslation_level_min?: number;
stars_stargift_resale_amount_max?: number;
stars_stargift_resale_amount_min?: 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,
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
channelRestrictAdsLevelMin: appConfig.channel_restrict_sponsored_level_min,
channelAutoTranslationLevelMin: appConfig.channel_autotranslation_level_min,
paidReactionMaxAmount: appConfig.stars_paid_reaction_amount_max,
isChannelRevenueWithdrawalEnabled: appConfig.channel_revenue_withdrawal_enabled,
isStarsGiftEnabled: appConfig.stars_gifts_enabled,

View File

@ -119,6 +119,8 @@ function buildApiChatFieldsFromPeerEntity(
hasGeo: channel?.hasGeo,
subscriptionUntil: channel?.subscriptionUntilDate,
paidMessagesStars: paidMessagesStars?.toJSNumber(),
level: channel?.level,
hasAutoTranslation: channel?.autotranslation,
...buildApiChatPermissions(peerEntity),
...buildApiChatRestrictions(peerEntity),

View File

@ -2087,3 +2087,16 @@ export async function fetchSponsoredPeer({ query }: { query: string }) {
if (!result || result instanceof GramJs.contacts.SponsoredPeersEmpty) return undefined;
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,
});
}

View File

@ -49,6 +49,8 @@ export interface ApiChat {
isForumAsMessages?: true;
boostLevel?: number;
botVerificationIconId?: string;
hasAutoTranslation?: true;
level?: number;
// Calls
isCallActive?: boolean;

View File

@ -235,6 +235,7 @@ export interface ApiAppConfig {
bandwidthPremiumUploadSpeedup?: number;
bandwidthPremiumDownloadSpeedup?: number;
channelRestrictAdsLevelMin?: number;
channelAutoTranslationLevelMin?: number;
paidReactionMaxAmount?: number;
isChannelRevenueWithdrawalEnabled?: boolean;
isStarsGiftEnabled?: boolean;

View File

@ -2007,4 +2007,5 @@
"ValueGiftSortByPrice" = "Price";
"ValueGiftSortByNumber" = "Number";
"ResellGiftsNoFound" = "No gifts found";
"ResellGiftsClearFilters" = "Clear Filters";
"ResellGiftsClearFilters" = "Clear Filters";
"AutomaticTranslation" = "Automatic Translation";

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import {
memo, useMemo, useRef, useState,
memo, useCallback, useMemo, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
@ -123,8 +123,8 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
language,
detectedChatLanguage,
doNotTranslate,
onTopicSearch,
isAccountFrozen,
onTopicSearch,
}) => {
const {
joinChannel,
@ -239,7 +239,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
handleSearchClick();
});
const getTextWithLanguage = useLastCallback((langKey: string, langCode: string) => {
const getTextWithLanguage = useCallback((langKey: string, langCode: string) => {
const simplified = langCode.split('-')[0];
const translationKey = `TranslateLanguage${simplified.toUpperCase()}`;
const name = lang(translationKey);
@ -250,7 +250,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
const translatedNames = new Intl.DisplayNames([language], { type: 'language' });
const translatedName = translatedNames.of(langCode)!;
return lang(`${langKey}Other`, translatedName);
});
}, [language, lang]);
const buttonText = useMemo(() => {
if (isTranslating) return lang('ShowOriginalButton');

View File

@ -32,6 +32,7 @@ import {
} from '../../global/helpers';
import {
selectBot,
selectCanTranslateChat,
selectChat,
selectChatFullInfo,
selectChatLastMessage,
@ -52,6 +53,7 @@ import {
selectTabState,
selectThreadInfo,
selectTopic,
selectTranslationLanguage,
selectUserFullInfo,
} from '../../global/selectors';
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
@ -92,13 +94,13 @@ type OwnProps = {
isComments?: boolean;
canPost: boolean;
isReady: boolean;
onScrollDownToggle: BooleanToVoidFunction;
onNotchToggle: BooleanToVoidFunction;
withBottomShift?: boolean;
withDefaultBg: boolean;
onIntersectPinnedMessage: OnIntersectPinnedMessage;
isContactRequirePremium?: boolean;
paidMessagesStars?: number;
onScrollDownToggle: BooleanToVoidFunction;
onNotchToggle: BooleanToVoidFunction;
onIntersectPinnedMessage: OnIntersectPinnedMessage;
};
type StateProps = {
@ -137,6 +139,9 @@ type StateProps = {
isChatProtected?: boolean;
hasCustomGreeting?: boolean;
isAppConfigLoaded?: boolean;
canTranslate?: boolean;
translationLanguage?: string;
shouldAutoTranslate?: boolean;
};
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
@ -196,16 +201,19 @@ const MessageList: FC<OwnProps & StateProps> = ({
areAdsEnabled,
channelJoinInfo,
isChatProtected,
onIntersectPinnedMessage,
onScrollDownToggle,
onNotchToggle,
isAccountFrozen,
hasCustomGreeting,
isAppConfigLoaded,
canTranslate,
translationLanguage,
shouldAutoTranslate,
onIntersectPinnedMessage,
onScrollDownToggle,
onNotchToggle,
}) => {
const {
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
loadMessageViews, loadPeerStoriesByIds, loadFactChecks,
loadMessageViews, loadPeerStoriesByIds, loadFactChecks, requestChatTranslation,
} = getActions();
const containerRef = useRef<HTMLDivElement>();
@ -268,6 +276,12 @@ const MessageList: FC<OwnProps & StateProps> = ({
memoFocusingIdRef.current = 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);
const messageGroups = useMemo(() => {
@ -806,6 +820,10 @@ export default memo(withGlobal<OwnProps>(
const hasCustomGreeting = Boolean(userFullInfo?.businessIntro);
const isAppConfigLoaded = global.isAppConfigLoaded;
const canTranslate = selectCanTranslateChat(global, chatId) && !chatFullInfo?.isTranslationDisabled;
const shouldAutoTranslate = chat?.hasAutoTranslation;
const translationLanguage = selectTranslationLanguage(global);
return {
areAdsEnabled,
isChatLoaded: true,
@ -842,6 +860,9 @@ export default memo(withGlobal<OwnProps>(
isAccountFrozen,
hasCustomGreeting,
isAppConfigLoaded,
canTranslate,
translationLanguage,
shouldAutoTranslate,
};
},
)(MessageList));

View File

@ -747,7 +747,7 @@ const Message: FC<OwnProps & StateProps> = ({
const detectedLanguage = useTextLanguage(
text?.text,
!(areTranslationsEnabled || shouldDetectChatLanguage),
!(areTranslationsEnabled && shouldDetectChatLanguage),
getIsMessageListReady,
);
useDetectChatLanguage(message, detectedLanguage, !shouldDetectChatLanguage, getIsMessageListReady);

View File

@ -30,13 +30,25 @@ export default function useDetectChatLanguage(
if (isDisabled || (getIsReady && !getIsReady())) return;
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]);
}
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 previousMetadata = chatStats.get(id);
@ -90,7 +102,7 @@ function makeChatDecision(chatId: string) {
}
const translatableRatio = translatableCount / messagesChecked;
const detectableRatio = detectableCount / messagesChecked;
const detectableRatio = detectableCount / translatableCount;
if (translatableRatio < MIN_TRANSLATABLE_RATIO || detectableRatio < MIN_DETECTABLE_RATIO) {
return;

View File

@ -1,7 +1,7 @@
import type { ChangeEvent } from 'react';
import type { FC } from '../../../lib/teact/teact';
import {
memo, useCallback, useEffect, useMemo, useState,
memo, useEffect, useMemo, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
@ -11,12 +11,13 @@ import type {
import { ApiMediaFormat } from '../../../api/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 { formatInteger } from '../../../util/textFormat';
import useFlag from '../../../hooks/useFlag';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia';
import useOldLang from '../../../hooks/useOldLang';
@ -27,6 +28,7 @@ import FloatingActionButton from '../../ui/FloatingActionButton';
import InputText from '../../ui/InputText';
import ListItem from '../../ui/ListItem';
import Spinner from '../../ui/Spinner';
import Switcher from '../../ui/Switcher';
import TextArea from '../../ui/TextArea';
import './Management.scss';
@ -46,6 +48,8 @@ type StateProps = {
canInvite?: boolean;
exportedInvites?: ApiExportedInvite[];
availableReactions?: ApiAvailableReaction[];
hasAutoTranslation?: boolean;
canToggleAutoTranslation?: boolean;
};
const CHANNEL_TITLE_EMPTY = 'Channel title can\'t be empty';
@ -63,6 +67,8 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
availableReactions,
onScreenSelect,
onClose,
hasAutoTranslation,
canToggleAutoTranslation,
}) => {
const {
updateChat,
@ -72,6 +78,7 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
openChat,
loadExportedChatInvites,
loadChatJoinRequests,
toggleAutoTranslation,
} = getActions();
const currentTitle = chat?.title || '';
@ -88,6 +95,12 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
const currentAvatarBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
const lang = useOldLang();
const hasAutoTranslationAvailable = chat && isChatChannel(chat);
const handleAutoTranslationChange = useLastCallback(() => {
toggleAutoTranslation({ chatId, isEnabled: !hasAutoTranslation });
});
useHistoryBack({
isActive,
onBack: onClose,
@ -112,46 +125,46 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
}, [chatFullInfo?.adminMembersById]);
const removedUsersCount = chatFullInfo?.kickedMembers?.length || 0;
const handleClickEditType = useCallback(() => {
const handleClickEditType = useLastCallback(() => {
onScreenSelect(ManagementScreens.ChatPrivacyType);
}, [onScreenSelect]);
});
const handleClickDiscussion = useCallback(() => {
const handleClickDiscussion = useLastCallback(() => {
onScreenSelect(ManagementScreens.Discussion);
}, [onScreenSelect]);
});
const handleClickReactions = useCallback(() => {
const handleClickReactions = useLastCallback(() => {
onScreenSelect(ManagementScreens.Reactions);
}, [onScreenSelect]);
});
const handleClickAdministrators = useCallback(() => {
const handleClickAdministrators = useLastCallback(() => {
onScreenSelect(ManagementScreens.ChatAdministrators);
}, [onScreenSelect]);
});
const handleClickInvites = useCallback(() => {
const handleClickInvites = useLastCallback(() => {
onScreenSelect(ManagementScreens.Invites);
}, [onScreenSelect]);
});
const handleClickRequests = useCallback(() => {
const handleClickRequests = useLastCallback(() => {
onScreenSelect(ManagementScreens.JoinRequests);
}, [onScreenSelect]);
});
const handleSetPhoto = useCallback((file: File) => {
const handleSetPhoto = useLastCallback((file: File) => {
setPhoto(file);
setIsProfileFieldsTouched(true);
}, []);
});
const handleTitleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const handleTitleChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
setIsProfileFieldsTouched(true);
}, []);
});
const handleAboutChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
const handleAboutChange = useLastCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
setAbout(e.target.value);
setIsProfileFieldsTouched(true);
}, []);
});
const handleUpdateChannel = useCallback(() => {
const handleUpdateChannel = useLastCallback(() => {
const trimmedTitle = title.trim();
const trimmedAbout = about.trim();
@ -166,17 +179,17 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
about: trimmedAbout,
photo,
});
}, [about, chatId, photo, title, updateChat]);
});
const handleClickSubscribers = useCallback(() => {
const handleClickSubscribers = useLastCallback(() => {
onScreenSelect(ManagementScreens.ChannelSubscribers);
}, [onScreenSelect]);
});
const handleRemovedUsersClick = useCallback(() => {
const handleRemovedUsersClick = useLastCallback(() => {
onScreenSelect(ManagementScreens.ChannelRemovedUsers);
}, [onScreenSelect]);
});
const handleDeleteChannel = useCallback(() => {
const handleDeleteChannel = useLastCallback(() => {
if (chat.isCreator) {
deleteChannel({ chatId: chat.id });
} else {
@ -186,7 +199,7 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
closeDeleteDialog();
closeManagement();
openChat({ id: undefined });
}, [chat.isCreator, chat.id, closeDeleteDialog, closeManagement, leaveChannel, deleteChannel, openChat]);
});
const chatReactionsDescription = useMemo(() => {
if (!chatFullInfo?.enabledReactions) {
@ -290,6 +303,22 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
{chatReactionsDescription}
</span>
</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 className="section">
<ListItem
@ -353,7 +382,11 @@ export default memo(withGlobal<OwnProps>(
const { management } = selectTabState(global);
const { progress } = management;
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 {
chat,
chatFullInfo: selectChatFullInfo(global, chatId),
@ -362,6 +395,8 @@ export default memo(withGlobal<OwnProps>(
canInvite: getHasAdminRight(chat, 'inviteUsers'),
exportedInvites: invites,
availableReactions: global.reactions.availableReactions,
hasAutoTranslation,
canToggleAutoTranslation,
};
},
(global, { chatId }) => {

View File

@ -15,7 +15,7 @@ import renderText from '../../common/helpers/renderText';
import useFlag from '../../../hooks/useFlag';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useLastCallback from '../../../hooks/useLastCallback.ts';
import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
@ -154,14 +154,14 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
if (isChatPublic(linkedGroup)) {
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'],
);
}
return renderText(
// 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'],
);
}
@ -169,12 +169,14 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
function renderLinkedGroup() {
return (
<div>
<ListItem
className="chat-item-clickable"
inactive
>
<GroupChatInfo chatId={linkedChat!.id} />
</ListItem>
{linkedChat && (
<ListItem
className="chat-item-clickable"
inactive
>
<GroupChatInfo chatId={linkedChat.id} />
</ListItem>
)}
<ListItem
icon="delete"
ripple
@ -188,7 +190,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
onClose={closeConfirmUnlinkGroupDialog}
header={renderUnlinkGroupHeader()}
textParts={renderText(
lang(isChannel ? 'DiscussionUnlinkChannelAlert' : 'DiscussionUnlinkGroupAlert', linkedChat!.title),
lang(isChannel ? 'DiscussionUnlinkChannelAlert' : 'DiscussionUnlinkGroupAlert', linkedChat?.title),
['br', 'simple_markdown'],
)}
confirmLabel={lang(isChannel ? 'DiscussionUnlinkGroup' : 'DiscussionUnlinkChannel')}

View File

@ -134,3 +134,7 @@
right: 0.5rem;
transform: translate(0, -50%);
}
.boostInfo {
margin: 0 -1rem;
}

View File

@ -226,8 +226,7 @@ const BoostStatistics = ({
return (
<ListItem
className="chat-item-clickable"
className={buildClassName(styles.boostInfo, 'chat-item-clickable')}
onClick={() => handleBoosterClick(boost.userId)}
>
<PrivateChatInfo

View File

@ -2984,6 +2984,19 @@ addActionHandler('updatePaidMessagesPrice', async (global, actions, payload): Pr
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> => {
const { slug, tabId = getCurrentTabId() } = payload;
const result = await callApi('resolveBusinessChatLink', { slug });

View File

@ -283,6 +283,9 @@ export function selectShouldDetectChatLanguage<T extends GlobalState>(
) {
const chat = selectChat(global, chatId);
if (!chat) return false;
if (chat.hasAutoTranslation) return true;
const { canTranslateChats } = global.settings.byKey;
const isPremium = selectIsCurrentUserPremium(global);

View File

@ -1081,6 +1081,10 @@ export interface ActionPayloads {
chatId: string;
paidMessagesStars: number;
} & WithTabId;
toggleAutoTranslation: {
chatId: string;
isEnabled: boolean;
} & WithTabId;
updateChat: {
chatId: string;

View File

@ -421,7 +421,7 @@ namespace Api {
export type TypeAccessPointRule = AccessPointRule;
export type TypeTlsClientHello = TlsClientHello;
export type TypeTlsBlock = TlsBlockString | TlsBlockRandom | TlsBlockZero | TlsBlockDomain | TlsBlockGrease | TlsBlockScope;
export namespace storage {
export type TypeFileType = storage.FileUnknown | storage.FilePartial | storage.FileJpeg | storage.FileGif | storage.FilePng | storage.FilePdf | storage.FileMp3 | storage.FileMov | storage.FileMp4 | storage.FileWebp;
@ -17724,7 +17724,7 @@ namespace Api {
static fromReader(reader: Reader): TlsBlockScope;
}
export namespace storage {
export class FileUnknown extends VirtualClass<void> {
@ -22053,7 +22053,7 @@ namespace Api {
}>, Api.TypeDestroySessionRes> {
sessionId: long;
}
export namespace auth {
export class SendCode extends Request<Partial<{

View File

@ -1727,6 +1727,7 @@ channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool =
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.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.canSendMessage#1359f4e6 bot:InputUser = Bool;
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
@ -1834,4 +1835,4 @@ premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:
premium.getMyBoosts#be77b4a = premium.MyBoosts;
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`;
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`;

View File

@ -280,6 +280,7 @@
"channels.searchPosts",
"channels.reportSpam",
"channels.updatePaidMessagesPrice",
"channels.toggleAutotranslation",
"bots.getBotRecommendations",
"bots.canSendMessage",
"bots.allowSendMessage",

View File

@ -1530,6 +1530,7 @@ export interface LangPair {
'ValueGiftSortByNumber': undefined;
'ResellGiftsNoFound': undefined;
'ResellGiftsClearFilters': undefined;
'AutomaticTranslation': undefined;
}
export interface LangPairWithVariables<V = LangVariable> {
@ -1773,6 +1774,9 @@ export interface LangPairWithVariables<V = LangVariable> {
'VoipPeerIncompatible': {
'user': V;
};
'NewDiscussionChatTitle': {
'name': V;
};
'LastSeenTodayAt': {
'time': V;
};
@ -2170,6 +2174,9 @@ export interface LangPairWithVariables<V = LangVariable> {
'from': V;
'title': V;
};
'ActionCreatedChatYou': {
'title': V;
};
'ActionPaymentDone': {
'amount': V;
'user': V;
@ -2488,9 +2495,6 @@ export interface LangPairWithVariables<V = LangVariable> {
'ComposerTitleForwardFrom': {
'users': V;
};
'NewDiscussionChatTitle': {
'name': V;
}
}
export interface LangPairPlural {