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 { IconName } from '../../types/icons';
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 { ApiBotVerification } from './peers';
import type { ApiStarsSubscriptionPricing } from './stars';
@ -143,6 +151,27 @@ export type ApiNotification = {
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 = {
message: string;
entities?: ApiMessageEntity[];

View File

@ -663,7 +663,8 @@
"AttachDocument" = "File";
"Poll" = "Poll";
"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...";
"Message" = "Message";
"RecentStickers" = "Recently Used";
@ -737,6 +738,8 @@
"ErrorPasswordChanged" = "Password has been changed, please try again";
"ErrorPasswordMissing" = "You must set 2FA password to use this feature";
"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";
"ErrorUnspecified" = "Error";
"NoStickers" = "No stickers yet";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,6 @@ import type {
} from '../../types';
import {
type ApiChat,
type ApiContact,
type ApiInputMessageReplyInfo,
type ApiPeer,
type ApiUrlAuthResult,
@ -138,11 +137,15 @@ addActionHandler('clickBotInlineButton', (global, actions, payload): ActionRetur
}
actions.showDialog({
data: {
phoneNumber: user.phoneNumber,
firstName: user.firstName || '',
lastName: user.lastName || '',
userId: user.id,
} as ApiContact,
type: 'contact',
contact: {
mediaType: 'contact',
phoneNumber: user.phoneNumber,
firstName: user.firstName || '',
lastName: user.lastName || '',
userId: user.id,
},
},
tabId,
});
break;
@ -1438,7 +1441,7 @@ async function answerCallbackButton<T extends GlobalState>(
const { message, alert: isError, url } = result;
if (isError) {
showDialog({ data: { message: message || 'Error' }, tabId });
showDialog({ data: { type: 'error', message: message || 'Error' }, tabId });
} else if (message) {
showNotification({ message, tabId });
} 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') {
actions.openLimitReachedModal({ limit: 'channels', tabId });
} 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') {
actions.openLimitReachedModal({ limit: 'channels', tabId });
} 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') {
actions.showNotification({ message: langProvider.oldTranslate('FloodWait'), tabId });
} 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') {
actions.openLimitReachedModal({ limit: 'chatlistJoined', tabId });
} 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.openSettingsScreen({ screen: SettingsScreens.Folders, tabId });
} 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);
} catch (error) {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} finally {
global = getGlobal();
@ -3532,7 +3532,7 @@ export async function migrateChat<T extends GlobalState>(
if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') {
actions.openLimitReachedModal({ limit: 'channels', tabId });
} else {
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
}
return undefined;

View File

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

View File

@ -1329,5 +1329,5 @@ function handlePaymentFormError(error: string, tabId: number) {
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)) {
actions.closePaymentModal({ tabId });
} 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 type { ActionReturnType, GlobalState } from '../../types';
import { type ApiError } from '../../../api/types';
import {
ANIMATION_WAVE_MIN_INTERVAL,
@ -389,13 +388,15 @@ addActionHandler('showDialog', (global, actions, payload): ActionReturnType => {
const { data, tabId = getCurrentTabId() } = payload;
// 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;
}
const newDialogs = [...selectTabState(global, tabId).dialogs];
if ('message' in data) {
const existingErrorIndex = newDialogs.findIndex((err) => (err as ApiError).message === data.message);
if (data.type === 'error') {
const existingErrorIndex = newDialogs.findIndex((dialog) => {
return dialog.type === 'error' && dialog.message === data.message;
});
if (existingErrorIndex !== -1) {
newDialogs.splice(existingErrorIndex, 1);
}

View File

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

View File

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

View File

@ -86,7 +86,7 @@ if (IS_SERVICE_WORKER_SUPPORTED) {
}
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) {