Dialog: Migrate messages to new lang API (#6800)

This commit is contained in:
Alexander Zinchuk 2026-04-14 14:35:45 +02:00
parent 85c6df27b8
commit aafb84c5e1
16 changed files with 144 additions and 50 deletions

View File

@ -1,7 +1,15 @@
import type { CallbackAction } from '../../global/types'; import type { CallbackAction } from '../../global/types';
import type { IconName } from '../../types/icons'; import type { IconName } from '../../types/icons';
import type { LangFnParameters, RegularLangFnParameters } from '../../util/localization'; import type { LangFnParameters, RegularLangFnParameters } from '../../util/localization';
import type { ApiDocument, ApiFormattedText, ApiMessageEntity, ApiPhoto, ApiReaction, ApiVideo } from './messages'; import type {
ApiContact,
ApiDocument,
ApiFormattedText,
ApiMessageEntity,
ApiPhoto,
ApiReaction,
ApiVideo,
} from './messages';
import type { ApiPremiumSection } from './payments'; import type { ApiPremiumSection } from './payments';
import type { ApiBotVerification } from './peers'; import type { ApiBotVerification } from './peers';
import type { ApiStarsSubscriptionPricing } from './stars'; import type { ApiStarsSubscriptionPricing } from './stars';
@ -143,6 +151,27 @@ export type ApiNotification = {
messageEntities?: undefined; messageEntities?: undefined;
}); });
export type ApiDialogError = {
type: 'error';
} & ApiError;
export type ApiDialogMessage = {
type: 'message';
text: ApiFormattedText;
};
export type ApiDialogContact = {
type: 'contact';
contact: ApiContact;
};
export type ApiDialogLocalizedMessage = {
type: 'localized';
text: LangFnParameters;
};
export type ApiDialog = ApiDialogError | ApiDialogMessage | ApiDialogContact | ApiDialogLocalizedMessage;
export type ApiError = { export type ApiError = {
message: string; message: string;
entities?: ApiMessageEntity[]; entities?: ApiMessageEntity[];

View File

@ -663,7 +663,8 @@
"AttachDocument" = "File"; "AttachDocument" = "File";
"Poll" = "Poll"; "Poll" = "Poll";
"SlowModePlaceholder" = "Next message in {timer}"; "SlowModePlaceholder" = "Next message in {timer}";
"SlowModeHint" = "Slow Mode is active. You can send\nyour next message in {time}."; "SlowModeHint" = "Slow Mode is active. You can send your next message in {time}.";
"NoVoiceMessagesAllowed" = "**{user}** doesn't accept voice messages.";
"SendMessageAsTitle" = "Send message as..."; "SendMessageAsTitle" = "Send message as...";
"Message" = "Message"; "Message" = "Message";
"RecentStickers" = "Recently Used"; "RecentStickers" = "Recently Used";
@ -737,6 +738,8 @@
"ErrorPasswordChanged" = "Password has been changed, please try again"; "ErrorPasswordChanged" = "Password has been changed, please try again";
"ErrorPasswordMissing" = "You must set 2FA password to use this feature"; "ErrorPasswordMissing" = "You must set 2FA password to use this feature";
"ErrorPasskeyUnknown" = "This passkey is not assigned to any account"; "ErrorPasskeyUnknown" = "This passkey is not assigned to any account";
"ErrorMessageTooLong_one" = "The provided message is too long. Please remove {count} character.";
"ErrorMessageTooLong_other" = "The provided message is too long. Please remove {count} characters.";
"ErrorUrlExpired" = "This link has expired"; "ErrorUrlExpired" = "This link has expired";
"ErrorUnspecified" = "Error"; "ErrorUnspecified" = "Error";
"NoStickers" = "No stickers yet"; "NoStickers" = "No stickers yet";

View File

@ -1050,12 +1050,16 @@ const Composer = ({
const extraLength = text.length - maxLength; const extraLength = text.length - maxLength;
showDialog({ showDialog({
data: { data: {
message: 'MESSAGE_TOO_LONG_PLEASE_REMOVE_CHARACTERS', type: 'localized',
textParams: { text: {
'{EXTRA_CHARS_COUNT}': extraLength.toString(), key: 'ErrorMessageTooLong',
'{PLURAL_S}': extraLength > 1 ? 's' : '', variables: {
count: extraLength,
},
options: {
pluralValue: extraLength,
},
}, },
hasErrorKey: true,
}, },
}); });
@ -1080,11 +1084,16 @@ const Composer = ({
const secondsRemaining = nextSendDateNotReached const secondsRemaining = nextSendDateNotReached
? slowMode.nextSendDate! - nowSeconds ? slowMode.nextSendDate! - nowSeconds
: slowMode.seconds - secondsSinceLastMessage!; : slowMode.seconds - secondsSinceLastMessage!;
showDialog({ showDialog({
data: { data: {
message: oldLang('SlowModeHint', formatMediaDuration(secondsRemaining)), type: 'localized',
isSlowMode: true, text: {
hasErrorKey: false, key: 'SlowModeHint',
variables: {
time: formatMediaDuration(secondsRemaining),
},
},
}, },
}); });

View File

@ -733,7 +733,7 @@ function processEntityAsHtml(
case ApiMessageEntityTypes.TextUrl: case ApiMessageEntityTypes.TextUrl:
return `<a return `<a
class="text-entity-link" class="text-entity-link"
href=${getLinkUrl(rawEntityText, entity)} href="${getLinkUrl(rawEntityText, entity)}"
data-entity-type="${entity.type}" data-entity-type="${entity.type}"
dir="auto" dir="auto"
>${renderedContent}</a>`; >${renderedContent}</a>`;

View File

@ -1,8 +1,13 @@
import type { TeactNode } from '../../lib/teact/teact';
import { memo, useEffect } from '../../lib/teact/teact'; import { memo, useEffect } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { import type {
ApiContact, ApiError, ApiContact,
ApiDialog,
ApiDialogError,
ApiDialogLocalizedMessage,
ApiDialogMessage,
} from '../../api/types'; } from '../../api/types';
import type { MessageList } from '../../types'; import type { MessageList } from '../../types';
@ -18,7 +23,7 @@ import Modal from '../ui/Modal';
type StateProps = { type StateProps = {
currentMessageList?: MessageList; currentMessageList?: MessageList;
dialogs: (ApiError | ApiContact)[]; dialogs: ApiDialog[];
}; };
const Dialogs = ({ dialogs, currentMessageList }: StateProps) => { const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
@ -81,17 +86,16 @@ const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
); );
}; };
const renderError = (error: ApiError) => { const renderTextDialog = (renderedText: TeactNode, title = 'Telegram') => {
return ( return (
<Modal <Modal
isOpen={isModalOpen} isOpen={isModalOpen}
onClose={closeModal} onClose={closeModal}
onCloseAnimationEnd={dismissDialog} onCloseAnimationEnd={dismissDialog}
className="error" className="error"
title={getErrorHeader(error)} title={title}
> >
{error.hasErrorKey ? getReadableErrorText(error) {renderedText}
: renderTextWithEntities({ text: error.message, entities: error.entities })}
<div className="dialog-buttons mt-2"> <div className="dialog-buttons mt-2">
<Button isText onClick={closeModal}>{lang('OK')}</Button> <Button isText onClick={closeModal}>{lang('OK')}</Button>
</div> </div>
@ -99,9 +103,37 @@ const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
); );
}; };
const renderDialog = (dialog: ApiError | ApiContact) => { const renderFormattedTextDialog = (dialog: ApiDialogMessage, title?: string) => {
if ('phoneNumber' in dialog) { const renderedText = renderTextWithEntities(dialog.text);
return renderContactRequest(dialog);
return renderTextDialog(renderedText, title);
};
const renderLocalizedDialog = (dialog: ApiDialogLocalizedMessage, title?: string) => {
return renderTextDialog(lang.with(dialog.text), title);
};
const renderError = (error: ApiDialogError) => {
const renderedErrorMessage = error.hasErrorKey
? getReadableErrorText(error)
: error.entities?.length
? renderTextWithEntities({ text: error.message, entities: error.entities })
: error.message;
return renderTextDialog(renderedErrorMessage, getErrorHeader(error));
};
const renderDialog = (dialog: ApiDialog) => {
if (dialog.type === 'contact') {
return renderContactRequest(dialog.contact);
}
if (dialog.type === 'message') {
return renderFormattedTextDialog(dialog);
}
if (dialog.type === 'localized') {
return renderLocalizedDialog(dialog);
} }
return renderError(dialog); return renderError(dialog);
@ -110,7 +142,7 @@ const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
return Boolean(dialogs.length) && renderDialog(dialogs[dialogs.length - 1]); return Boolean(dialogs.length) && renderDialog(dialogs[dialogs.length - 1]);
}; };
function getErrorHeader(error: ApiError) { function getErrorHeader(error: ApiDialogError) {
if (error.isSlowMode) { if (error.isSlowMode) {
return 'Slowmode enabled'; return 'Slowmode enabled';
} }

View File

@ -71,7 +71,10 @@ const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
const handleSponsorInfo = useLastCallback(() => { const handleSponsorInfo = useLastCallback(() => {
showDialog({ showDialog({
data: { data: {
message: [sponsorInfo, additionalInfo].filter(Boolean).join('\n'), type: 'message',
text: {
text: [sponsorInfo, additionalInfo].filter(Boolean).join('\n'),
},
}, },
}); });
handleItemClick(); handleItemClick();

View File

@ -402,8 +402,9 @@
user-select: none; user-select: none;
display: flex; display: flex;
align-items: center;
gap: 0.25rem; gap: 0.25rem;
align-items: center;
margin-left: 1rem; margin-left: 1rem;
} }
@ -428,10 +429,8 @@
.sender-boosts { .sender-boosts {
user-select: none; user-select: none;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 0.75rem; font-size: 0.75rem;
} }
} }

View File

@ -6,7 +6,6 @@ import type {
} from '../../types'; } from '../../types';
import { import {
type ApiChat, type ApiChat,
type ApiContact,
type ApiInputMessageReplyInfo, type ApiInputMessageReplyInfo,
type ApiPeer, type ApiPeer,
type ApiUrlAuthResult, type ApiUrlAuthResult,
@ -138,11 +137,15 @@ addActionHandler('clickBotInlineButton', (global, actions, payload): ActionRetur
} }
actions.showDialog({ actions.showDialog({
data: { data: {
phoneNumber: user.phoneNumber, type: 'contact',
firstName: user.firstName || '', contact: {
lastName: user.lastName || '', mediaType: 'contact',
userId: user.id, phoneNumber: user.phoneNumber,
} as ApiContact, firstName: user.firstName || '',
lastName: user.lastName || '',
userId: user.id,
},
},
tabId, tabId,
}); });
break; break;
@ -1438,7 +1441,7 @@ async function answerCallbackButton<T extends GlobalState>(
const { message, alert: isError, url } = result; const { message, alert: isError, url } = result;
if (isError) { if (isError) {
showDialog({ data: { message: message || 'Error' }, tabId }); showDialog({ data: { type: 'error', message: message || 'Error' }, tabId });
} else if (message) { } else if (message) {
showNotification({ message, tabId }); showNotification({ message, tabId });
} else if (url) { } else if (url) {

View File

@ -789,7 +789,7 @@ addActionHandler('createChannel', async (global, actions, payload): Promise<void
if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') { if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') {
actions.openLimitReachedModal({ limit: 'channels', tabId }); actions.openLimitReachedModal({ limit: 'channels', tabId });
} else { } else {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId }); actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} }
} }
@ -847,7 +847,7 @@ addActionHandler('joinChannel', async (global, actions, payload): Promise<void>
if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') { if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') {
actions.openLimitReachedModal({ limit: 'channels', tabId }); actions.openLimitReachedModal({ limit: 'channels', tabId });
} else { } else {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId }); actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} }
} }
}); });
@ -2651,7 +2651,7 @@ addActionHandler('toggleForum', async (global, actions, payload): Promise<void>
if ((error as ApiError).message === 'FLOOD') { if ((error as ApiError).message === 'FLOOD') {
actions.showNotification({ message: langProvider.oldTranslate('FloodWait'), tabId }); actions.showNotification({ message: langProvider.oldTranslate('FloodWait'), tabId });
} else { } else {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId }); actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} }
} }
@ -2860,7 +2860,7 @@ addActionHandler('joinChatlistInvite', async (global, actions, payload): Promise
if ((error as ApiError).message === 'CHATLISTS_TOO_MUCH') { if ((error as ApiError).message === 'CHATLISTS_TOO_MUCH') {
actions.openLimitReachedModal({ limit: 'chatlistJoined', tabId }); actions.openLimitReachedModal({ limit: 'chatlistJoined', tabId });
} else { } else {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId }); actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} }
} }
}); });
@ -2946,7 +2946,7 @@ addActionHandler('createChatlistInvite', async (global, actions, payload): Promi
actions.openLimitReachedModal({ limit: 'chatlistInvites', tabId }); actions.openLimitReachedModal({ limit: 'chatlistInvites', tabId });
actions.openSettingsScreen({ screen: SettingsScreens.Folders, tabId }); actions.openSettingsScreen({ screen: SettingsScreens.Folders, tabId });
} else { } else {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId }); actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} }
} }
@ -3031,7 +3031,7 @@ addActionHandler('editChatlistInvite', async (global, actions, payload): Promise
}; };
setGlobal(global); setGlobal(global);
} catch (error) { } catch (error) {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId }); actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} finally { } finally {
global = getGlobal(); global = getGlobal();
@ -3532,7 +3532,7 @@ export async function migrateChat<T extends GlobalState>(
if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') { if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') {
actions.openLimitReachedModal({ limit: 'channels', tabId }); actions.openLimitReachedModal({ limit: 'channels', tabId });
} else { } else {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId }); actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} }
return undefined; return undefined;

View File

@ -2567,7 +2567,17 @@ addActionHandler('setForwardChatOrTopic', async (global, actions, payload): Prom
if (isSelectForwardsContainVoiceMessages && user && !await checkIfVoiceMessagesAllowed(global, user, chatId)) { if (isSelectForwardsContainVoiceMessages && user && !await checkIfVoiceMessagesAllowed(global, user, chatId)) {
actions.showDialog({ actions.showDialog({
data: { data: {
message: oldTranslate('VoiceMessagesRestrictedByPrivacy', getUserFullName(user)), type: 'localized',
text: {
key: 'NoVoiceMessagesAllowed',
variables: {
user: getUserFullName(user),
},
options: {
withNodes: true,
withMarkdown: true,
},
},
}, },
tabId, tabId,
}); });

View File

@ -1329,5 +1329,5 @@ function handlePaymentFormError(error: string, tabId: number) {
return; return;
} }
getActions().showDialog({ data: { message: error, hasErrorKey: true }, tabId }); getActions().showDialog({ data: { type: 'error', message: error, hasErrorKey: true }, tabId });
} }

View File

@ -101,7 +101,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
} else if (shouldClosePaymentModal(update.error)) { } else if (shouldClosePaymentModal(update.error)) {
actions.closePaymentModal({ tabId }); actions.closePaymentModal({ tabId });
} else if (actions.showDialog) { } else if (actions.showDialog) {
actions.showDialog({ data: update.error, tabId }); actions.showDialog({ data: { type: 'error', ...update.error }, tabId });
} }
}); });

View File

@ -1,7 +1,6 @@
import { addCallback } from '../../../lib/teact/teactn'; import { addCallback } from '../../../lib/teact/teactn';
import type { ActionReturnType, GlobalState } from '../../types'; import type { ActionReturnType, GlobalState } from '../../types';
import { type ApiError } from '../../../api/types';
import { import {
ANIMATION_WAVE_MIN_INTERVAL, ANIMATION_WAVE_MIN_INTERVAL,
@ -389,13 +388,15 @@ addActionHandler('showDialog', (global, actions, payload): ActionReturnType => {
const { data, tabId = getCurrentTabId() } = payload; const { data, tabId = getCurrentTabId() } = payload;
// Filter out errors that we don't want to show to the user // Filter out errors that we don't want to show to the user
if ('message' in data && data.hasErrorKey && !getReadableErrorText(data)) { if (data.type === 'error' && data.hasErrorKey && !getReadableErrorText(data)) {
return global; return global;
} }
const newDialogs = [...selectTabState(global, tabId).dialogs]; const newDialogs = [...selectTabState(global, tabId).dialogs];
if ('message' in data) { if (data.type === 'error') {
const existingErrorIndex = newDialogs.findIndex((err) => (err as ApiError).message === data.message); const existingErrorIndex = newDialogs.findIndex((dialog) => {
return dialog.type === 'error' && dialog.message === data.message;
});
if (existingErrorIndex !== -1) { if (existingErrorIndex !== -1) {
newDialogs.splice(existingErrorIndex, 1); newDialogs.splice(existingErrorIndex, 1);
} }

View File

@ -10,9 +10,8 @@ import type {
ApiChatType, ApiChatType,
ApiCheckedGiftCode, ApiCheckedGiftCode,
ApiCollectibleInfo, ApiCollectibleInfo,
ApiContact, ApiDialog,
ApiEmojiStatusCollectible, ApiEmojiStatusCollectible,
ApiError,
ApiFormattedText, ApiFormattedText,
ApiGeoPoint, ApiGeoPoint,
ApiGlobalMessageSearchType, ApiGlobalMessageSearchType,
@ -490,7 +489,7 @@ export type TabState = {
}; };
notifications: ApiNotification[]; notifications: ApiNotification[];
dialogs: (ApiError | ApiContact)[]; dialogs: ApiDialog[];
safeLinkModalUrl?: string; safeLinkModalUrl?: string;
mapModal?: { mapModal?: {

View File

@ -2222,6 +2222,9 @@ export interface LangPairWithVariables<V = LangVariable> {
'SlowModeHint': { 'SlowModeHint': {
'time': V; 'time': V;
}; };
'NoVoiceMessagesAllowed': {
'user': V;
};
'ErrorFloodTime': { 'ErrorFloodTime': {
'time': V; 'time': V;
}; };
@ -3663,6 +3666,9 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
'PreviewSenderSendFile': { 'PreviewSenderSendFile': {
'count': V; 'count': V;
}; };
'ErrorMessageTooLong': {
'count': V;
};
'PinnedMessageTitle': { 'PinnedMessageTitle': {
'index': V; 'index': V;
}; };

View File

@ -86,7 +86,7 @@ if (IS_SERVICE_WORKER_SUPPORTED) {
} }
if (!IS_IOS && !IS_ANDROID && !IS_TEST) { if (!IS_IOS && !IS_ANDROID && !IS_TEST) {
getActions().showDialog?.({ data: { message: 'SERVICE_WORKER_DISABLED', hasErrorKey: true } }); getActions().showDialog?.({ data: { type: 'error', message: 'SERVICE_WORKER_DISABLED', hasErrorKey: true } });
} }
} }
} catch (err) { } catch (err) {