diff --git a/.vscode/settings.json b/.vscode/settings.json
index a32489e34a64..47310bec0703 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -14,10 +14,5 @@
}
],
"typescript.tsdk": "./node_modules/typescript/lib",
- "cSpell.words": [
- "livechat",
- "omnichannel",
- "photoswipe",
- "tmid"
- ]
+ "cSpell.words": ["katex", "livechat", "omnichannel", "photoswipe", "tmid"]
}
diff --git a/apps/meteor/app/autotranslate/client/lib/actionButton.ts b/apps/meteor/app/autotranslate/client/lib/actionButton.ts
index eb8cba74c34b..78bbabd0f37a 100644
--- a/apps/meteor/app/autotranslate/client/lib/actionButton.ts
+++ b/apps/meteor/app/autotranslate/client/lib/actionButton.ts
@@ -1,6 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
-import { isTranslatedMessage } from '@rocket.chat/core-typings';
import { AutoTranslate } from './autotranslate';
import { settings } from '../../../settings/client';
@@ -8,6 +7,10 @@ import { hasAtLeastOnePermission } from '../../../authorization/client';
import { MessageAction } from '../../../ui-utils/client/lib/MessageAction';
import { messageArgs } from '../../../../client/lib/utils/messageArgs';
import { Messages } from '../../../models/client';
+import {
+ hasTranslationLanguageInAttachments,
+ hasTranslationLanguageInMessage,
+} from '../../../../client/views/room/MessageList/lib/autoTranslate';
Meteor.startup(() => {
AutoTranslate.init();
@@ -22,8 +25,7 @@ Meteor.startup(() => {
action(_, props) {
const { message = messageArgs(this).msg } = props;
const language = AutoTranslate.getLanguage(message.rid);
- if (!isTranslatedMessage(message) || !message.translations[language]) {
- // } && !_.find(message.attachments, attachment => { return attachment.translations && attachment.translations[language]; })) {
+ if (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)) {
(AutoTranslate.messageIdsToWait as any)[message._id] = true;
Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } });
Meteor.call('autoTranslate.translateMessage', message, language);
@@ -31,12 +33,19 @@ Meteor.startup(() => {
const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set';
Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } });
},
- condition({ message, user }) {
+ condition({ message, subscription, user }) {
if (!user) {
return false;
}
+ const language = subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid) || '';
- return Boolean(message?.u && message.u._id !== user._id && isTranslatedMessage(message) && message.autoTranslateShowInverse);
+ return Boolean(
+ (message?.u &&
+ message.u._id !== user._id &&
+ subscription?.autoTranslate &&
+ (message as { autoTranslateShowInverse?: boolean }).autoTranslateShowInverse) ||
+ (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)),
+ );
},
order: 90,
});
@@ -48,8 +57,7 @@ Meteor.startup(() => {
action(_, props) {
const { message = messageArgs(this).msg } = props;
const language = AutoTranslate.getLanguage(message.rid);
- if (!isTranslatedMessage(message) || !message.translations[language]) {
- // } && !_.find(message.attachments, attachment => { return attachment.translations && attachment.translations[language]; })) {
+ if (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)) {
(AutoTranslate.messageIdsToWait as any)[message._id] = true;
Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } });
Meteor.call('autoTranslate.translateMessage', message, language);
@@ -57,12 +65,19 @@ Meteor.startup(() => {
const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set';
Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } });
},
- condition({ message, user }) {
+ condition({ message, subscription, user }) {
+ const language = subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid) || '';
if (!user) {
return false;
}
- return Boolean(message?.u && message.u._id !== user._id && isTranslatedMessage(message) && !message.autoTranslateShowInverse);
+ return Boolean(
+ message?.u &&
+ message.u._id !== user._id &&
+ subscription?.autoTranslate &&
+ !(message as { autoTranslateShowInverse?: boolean }).autoTranslateShowInverse &&
+ (hasTranslationLanguageInMessage(message, language) || hasTranslationLanguageInAttachments(message.attachments, language)),
+ );
},
order: 90,
});
diff --git a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
index 71ff7cc92c5a..199df2e5db71 100644
--- a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
+++ b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
@@ -9,10 +9,15 @@ import type {
IUser,
MessageAttachmentDefault,
} from '@rocket.chat/core-typings';
+import { isTranslatedMessageAttachment } from '@rocket.chat/core-typings';
import { Subscriptions, Messages } from '../../../models/client';
import { hasPermission } from '../../../authorization/client';
import { call } from '../../../../client/lib/utils/call';
+import {
+ hasTranslationLanguageInAttachments,
+ hasTranslationLanguageInMessage,
+} from '../../../../client/views/room/MessageList/lib/autoTranslate';
let userLanguage = 'en';
let username = '';
@@ -55,6 +60,9 @@ export const AutoTranslate = {
language: string,
autoTranslateShowInverse: boolean,
): MessageAttachmentDefault[] {
+ if (!isTranslatedMessageAttachment(attachments)) {
+ return attachments;
+ }
for (const attachment of attachments) {
if (attachment.author_name !== username) {
if (attachment.text && attachment.translations && attachment.translations[language]) {
@@ -134,16 +142,11 @@ export const createAutoTranslateMessageRenderer = (): ((message: ITranslatedMess
message.translations = {};
}
if (!!subscription?.autoTranslate !== !!message.autoTranslateShowInverse) {
- const hasAttachmentsTranslate =
- message.attachments?.some(
- (attachment) =>
- 'translations' in attachment &&
- typeof attachment.translations === 'object' &&
- autoTranslateLanguage in attachment.translations,
- ) ?? false;
-
message.translations.original = message.html;
- if (message.translations[autoTranslateLanguage] && !hasAttachmentsTranslate) {
+ if (
+ message.translations[autoTranslateLanguage] &&
+ !hasTranslationLanguageInAttachments(message.attachments, autoTranslateLanguage)
+ ) {
message.html = message.translations[autoTranslateLanguage];
}
@@ -155,12 +158,6 @@ export const createAutoTranslateMessageRenderer = (): ((message: ITranslatedMess
);
}
}
- } else if (message.attachments && message.attachments.length > 0) {
- message.attachments = AutoTranslate.translateAttachments(
- message.attachments,
- autoTranslateLanguage,
- !!message.autoTranslateShowInverse,
- );
}
return message;
};
@@ -177,7 +174,8 @@ export const createAutoTranslateMessageStreamHandler = (): ((message: ITranslate
subscription &&
subscription.autoTranslate === true &&
message.msg &&
- (!message.translations || !message.translations[language])
+ (!message.translations ||
+ (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)))
) {
// || (message.attachments && !_.find(message.attachments, attachment => { return attachment.translations && attachment.translations[language]; }))
Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } });
diff --git a/apps/meteor/app/autotranslate/server/autotranslate.ts b/apps/meteor/app/autotranslate/server/autotranslate.ts
index edb674f5a187..1d926fe5bb77 100644
--- a/apps/meteor/app/autotranslate/server/autotranslate.ts
+++ b/apps/meteor/app/autotranslate/server/autotranslate.ts
@@ -305,10 +305,13 @@ export abstract class AutoTranslate {
Meteor.defer(() => {
for (const [index, attachment] of message.attachments?.entries() ?? []) {
if (attachment.description || attachment.text) {
- const translations = this._translateAttachmentDescriptions(attachment, targetLanguages);
+ // Removes the initial link `[ ](quoterl)` from quote message before translation
+ const translatedText = attachment?.text?.replace(/\[(.*?)\]\(.*?\)/g, '$1') || attachment?.text;
+ const attachmentMessage = { ...attachment, text: translatedText };
+ const translations = this._translateAttachmentDescriptions(attachmentMessage, targetLanguages);
+
if (!_.isEmpty(translations)) {
Messages.addAttachmentTranslations(message._id, index, translations);
- Messages.addTranslations(message._id, translations, TranslationProviderRegistry[Provider]);
}
}
}
diff --git a/apps/meteor/app/autotranslate/server/googleTranslate.ts b/apps/meteor/app/autotranslate/server/googleTranslate.ts
index 91a068b880aa..135601d48efc 100644
--- a/apps/meteor/app/autotranslate/server/googleTranslate.ts
+++ b/apps/meteor/app/autotranslate/server/googleTranslate.ts
@@ -146,6 +146,7 @@ class GoogleAutoTranslate extends AutoTranslate {
params: {
key: this.apiKey,
target: language,
+ format: 'text',
},
query,
});
@@ -190,6 +191,7 @@ class GoogleAutoTranslate extends AutoTranslate {
params: {
key: this.apiKey,
target: language,
+ format: 'text',
},
query,
});
diff --git a/apps/meteor/app/ui-message/client/message.html b/apps/meteor/app/ui-message/client/message.html
index 6a571fa87c52..e3d9df72cb4b 100644
--- a/apps/meteor/app/ui-message/client/message.html
+++ b/apps/meteor/app/ui-message/client/message.html
@@ -55,7 +55,6 @@
{{#if showTranslated}}
- {{ translationProvider }}
{{/if}}
{{#if msg.sentByEmail}}
diff --git a/apps/meteor/app/ui-message/client/message.js b/apps/meteor/app/ui-message/client/message.js
index c32d94b75ad6..f0bffebb9a52 100644
--- a/apps/meteor/app/ui-message/client/message.js
+++ b/apps/meteor/app/ui-message/client/message.js
@@ -21,6 +21,7 @@ import { renderMessageBody } from '../../../client/lib/utils/renderMessageBody';
import { settings } from '../../settings/client';
import { formatTime } from '../../../client/lib/utils/formatTime';
import { formatDate } from '../../../client/lib/utils/formatDate';
+import { hasTranslationLanguageInAttachments } from '../../../client/views/room/MessageList/lib/autoTranslate';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
import './messageThread';
import './message.html';
@@ -258,7 +259,9 @@ Template.message.helpers({
const autoTranslate = subscription && subscription.autoTranslate;
return (
msg.autoTranslateFetching ||
- (!!autoTranslate !== !!msg.autoTranslateShowInverse && msg.translations && msg.translations[settings.translateLanguage])
+ (!!autoTranslate !== !!msg.autoTranslateShowInverse && msg.translations && msg.translations[settings.translateLanguage]) ||
+ (!!autoTranslate !== !!msg.autoTranslateShowInverse &&
+ hasTranslationLanguageInAttachments(msg.attachments, settings.translateLanguage))
);
}
},
diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts
index 9f5e79c71125..1ed1b5253281 100644
--- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts
+++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts
@@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import type { Icon } from '@rocket.chat/fuselage';
-import type { IMessage, IUser, ISubscription, IRoom, SettingValue, Serialized } from '@rocket.chat/core-typings';
+import type { IMessage, IUser, ISubscription, IRoom, SettingValue, Serialized, ITranslatedMessage } from '@rocket.chat/core-typings';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { Messages, Rooms, Subscriptions } from '../../../models/client';
@@ -13,6 +13,7 @@ import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
import type { ToolboxContextValue } from '../../../../client/views/room/contexts/ToolboxContext';
import type { ChatContext } from '../../../../client/views/room/contexts/ChatContext';
import { APIClient } from '../../../utils/client';
+import type { AutoTranslateOptions } from '../../../../client/views/room/MessageList/hooks/useAutoTranslate';
const getMessage = async (msgId: string): Promise | null> => {
try {
@@ -71,7 +72,14 @@ export type MessageActionConfig = {
tabbar,
room,
chat,
- }: { message?: IMessage; tabbar: ToolboxContextValue; room?: IRoom; chat: ContextType },
+ autoTranslateOptions,
+ }: {
+ message?: IMessage & Partial;
+ tabbar: ToolboxContextValue;
+ room?: IRoom;
+ chat: ContextType;
+ autoTranslateOptions?: AutoTranslateOptions;
+ },
) => any;
condition?: (props: MessageActionConditionProps) => Promise | boolean;
};
diff --git a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts
index 4ee0ced859c2..662da4ac343b 100644
--- a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts
+++ b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts
@@ -70,7 +70,14 @@ Meteor.startup(async function () {
label: 'Quote',
context: ['message', 'message-mobile', 'threads', 'federated'],
action(_, props) {
- const { message = messageArgs(this).msg, chat } = props;
+ const { message = messageArgs(this).msg, chat, autoTranslateOptions } = props;
+
+ if (message && autoTranslateOptions?.autoTranslateEnabled && autoTranslateOptions.showAutoTranslate(message)) {
+ message.msg =
+ message.translations && autoTranslateOptions.autoTranslateLanguage
+ ? message.translations[autoTranslateOptions.autoTranslateLanguage]
+ : message.msg;
+ }
chat?.composer?.quoteMessage(message);
},
diff --git a/apps/meteor/client/components/message/StatusIndicators.tsx b/apps/meteor/client/components/message/StatusIndicators.tsx
index d3bb297da4aa..f6eff61bef6e 100644
--- a/apps/meteor/client/components/message/StatusIndicators.tsx
+++ b/apps/meteor/client/components/message/StatusIndicators.tsx
@@ -1,6 +1,6 @@
import type { IMessage, ITranslatedMessage } from '@rocket.chat/core-typings';
import { isEditedMessage, isE2EEMessage, isOTRMessage } from '@rocket.chat/core-typings';
-import { MessageStatusIndicator, MessageStatusIndicatorItem, MessageStatusIndicatorText } from '@rocket.chat/fuselage';
+import { MessageStatusIndicator, MessageStatusIndicatorItem } from '@rocket.chat/fuselage';
import { useUserId, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';
@@ -10,7 +10,6 @@ import {
useShowStarred,
useShowTranslated,
useShowFollowing,
- useTranslateProvider,
} from '../../views/room/MessageList/contexts/MessageListContext';
type StatusIndicatorsProps = {
@@ -19,8 +18,7 @@ type StatusIndicatorsProps = {
const StatusIndicators = ({ message }: StatusIndicatorsProps): ReactElement => {
const t = useTranslation();
- const translated = useShowTranslated({ message });
- const translateProvider = useTranslateProvider({ message });
+ const translated = useShowTranslated(message);
const starred = useShowStarred({ message });
const following = useShowFollowing({ message });
@@ -33,11 +31,7 @@ const StatusIndicators = ({ message }: StatusIndicatorsProps): ReactElement => {
return (
- {translated && (
-
- {translateProvider}
-
- )}
+ {translated && }
{following && }
diff --git a/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx
index 8df0ddb34424..8aa541b6f1d8 100644
--- a/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx
@@ -4,6 +4,7 @@ import type { FC } from 'react';
import React from 'react';
import MarkdownText from '../../../../MarkdownText';
+import MessageContentBody from '../../../MessageContentBody';
import { useCollapse } from '../../../hooks/useCollapse';
import Attachment from '../structure/Attachment';
import AttachmentContent from '../structure/AttachmentContent';
@@ -22,13 +23,14 @@ export const AudioAttachment: FC = ({
description,
title_link: link,
title_link_download: hasDownload,
+ md,
}) => {
const [collapsed, collapse] = useCollapse(collapsedDefault);
const getURL = useMediaUrl();
return (
-
+ {md ? : }
{title}
diff --git a/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx
index 2f2b12d8e031..4bf59ec20603 100644
--- a/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx
@@ -4,6 +4,7 @@ import type { FC } from 'react';
import React from 'react';
import MarkdownText from '../../../../MarkdownText';
+import MessageContentBody from '../../../MessageContentBody';
import Attachment from '../structure/Attachment';
import AttachmentDescription from '../structure/AttachmentDescription';
import AttachmentDownload from '../structure/AttachmentDownload';
@@ -27,6 +28,7 @@ export const GenericFileAttachment: FC = ({
// format,
// name,
} = {},
+ md,
}) => {
// const [collapsed, collapse] = useCollapse(collapsedDefault);
const getURL = useMediaUrl();
@@ -34,7 +36,7 @@ export const GenericFileAttachment: FC = ({
{description && (
-
+ {md ? : }
)}
diff --git a/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx
index 90fc80487f7a..a5061c9a97dd 100644
--- a/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx
@@ -4,6 +4,7 @@ import type { FC } from 'react';
import React from 'react';
import MarkdownText from '../../../../MarkdownText';
+import MessageContentBody from '../../../MessageContentBody';
import { useCollapse } from '../../../hooks/useCollapse';
import Attachment from '../structure/Attachment';
import AttachmentContent from '../structure/AttachmentContent';
@@ -28,6 +29,7 @@ export const ImageAttachment: FC = ({
description,
title_link: link,
title_link_download: hasDownload,
+ md,
}) => {
const [loadImage, setLoadImage] = useLoadImage();
const [collapsed, collapse] = useCollapse(collapsedDefault);
@@ -36,7 +38,7 @@ export const ImageAttachment: FC = ({
{description && (
-
+ {md ? : }
)}
diff --git a/apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx
index f8525d8d9d9d..fa1296cfe048 100644
--- a/apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx
@@ -7,6 +7,7 @@ import React from 'react';
import { userAgentMIMETypeFallback } from '../../../../../lib/utils/userAgentMIMETypeFallback';
import MarkdownText from '../../../../MarkdownText';
+import MessageContentBody from '../../../MessageContentBody';
import { useCollapse } from '../../../hooks/useCollapse';
import Attachment from '../structure/Attachment';
import AttachmentContent from '../structure/AttachmentContent';
@@ -32,6 +33,7 @@ export const VideoAttachment: FC = ({
description,
title_link: link,
title_link_download: hasDownload,
+ md,
}) => {
const [collapsed, collapse] = useCollapse(collapsedDefault);
const getURL = useMediaUrl();
@@ -51,7 +53,7 @@ export const VideoAttachment: FC = ({
{description && (
-
+ {md ? : }
)}
diff --git a/apps/meteor/client/components/message/toolbox/Toolbox.tsx b/apps/meteor/client/components/message/toolbox/Toolbox.tsx
index 80420d4e1709..cf6ea04b157f 100644
--- a/apps/meteor/client/components/message/toolbox/Toolbox.tsx
+++ b/apps/meteor/client/components/message/toolbox/Toolbox.tsx
@@ -1,4 +1,4 @@
-import type { IMessage, IUser, IRoom } from '@rocket.chat/core-typings';
+import type { IMessage, IUser, IRoom, ITranslatedMessage } from '@rocket.chat/core-typings';
import { isThreadMessage, isRoomFederated } from '@rocket.chat/core-typings';
import { MessageToolbox, MessageToolboxItem } from '@rocket.chat/fuselage';
import { useUser, useUserSubscription, useSettings, useTranslation } from '@rocket.chat/ui-contexts';
@@ -9,6 +9,7 @@ import React, { memo, useMemo } from 'react';
import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction';
import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction';
import { useIsSelecting } from '../../../views/room/MessageList/contexts/SelectedMessagesContext';
+import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate';
import { useChat } from '../../../views/room/contexts/ChatContext';
import { useRoom } from '../../../views/room/contexts/RoomContext';
import { useToolboxContext } from '../../../views/room/contexts/ToolboxContext';
@@ -28,7 +29,7 @@ const getMessageContext = (message: IMessage, room: IRoom): MessageActionContext
};
type ToolboxProps = {
- message: IMessage;
+ message: IMessage & Partial;
};
const Toolbox = ({ message }: ToolboxProps): ReactElement | null => {
@@ -61,6 +62,8 @@ const Toolbox = ({ message }: ToolboxProps): ReactElement | null => {
const selecting = useIsSelecting();
+ const autoTranslateOptions = useAutoTranslate(subscription);
+
if (selecting) {
return null;
}
@@ -69,10 +72,10 @@ const Toolbox = ({ message }: ToolboxProps): ReactElement | null => {
{actionsQueryResult.data?.message.map((action) => (
action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions })}
key={action.id}
icon={action.icon}
title={t(action.label)}
- onClick={(e): void => action.action(e, { message, tabbar: toolbox, room, chat })}
data-qa-id={action.label}
data-qa-type='message-action-menu'
/>
@@ -82,7 +85,7 @@ const Toolbox = ({ message }: ToolboxProps): ReactElement | null => {
options={
actionsQueryResult.data?.menu.map((action) => ({
...action,
- action: (e): void => action.action(e, { message, tabbar: toolbox, room, chat }),
+ action: (e): void => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions }),
})) ?? []
}
data-qa-type='message-action-menu-options'
diff --git a/apps/meteor/client/components/message/variants/ThreadMessage.tsx b/apps/meteor/client/components/message/variants/ThreadMessage.tsx
index 7d7eb8118670..4c364098c8be 100644
--- a/apps/meteor/client/components/message/variants/ThreadMessage.tsx
+++ b/apps/meteor/client/components/message/variants/ThreadMessage.tsx
@@ -1,12 +1,13 @@
-import type { ISubscription, IThreadMessage, IThreadMainMessage } from '@rocket.chat/core-typings';
+import type { IThreadMessage, IThreadMainMessage } from '@rocket.chat/core-typings';
import { Message, MessageLeftContainer, MessageContainer } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
-import { useUserId } from '@rocket.chat/ui-contexts';
+import { useUserId, useUserSubscription } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useMemo, memo } from 'react';
import { useIsMessageHighlight } from '../../../views/room/MessageList/contexts/MessageHighlightContext';
import { useMessageListContext } from '../../../views/room/MessageList/contexts/MessageListContext';
+import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate';
import {
parseMessageTextToAstMarkdown,
removePossibleNullMessageValues,
@@ -21,7 +22,6 @@ import ThreadMessageContent from './thread/ThreadMessageContent';
type ThreadMessageProps = {
message: IThreadMessage | IThreadMainMessage;
- subscription?: ISubscription;
unread: boolean;
sequential: boolean;
};
@@ -34,7 +34,9 @@ const ThreadMessage = ({ message, sequential, unread }: ThreadMessageProps): Rea
actions: { openUserCard },
} = useMessageActions();
- const { autoTranslateLanguage, katex, showColors, useShowTranslated } = useMessageListContext();
+ const { katex, showColors } = useMessageListContext();
+ const subscription = useUserSubscription(message.rid);
+ const autoTranslateOptions = useAutoTranslate(subscription);
const normalizeMessage = useMemo(() => {
const parseOptions = {
@@ -48,8 +50,8 @@ const ThreadMessage = ({ message, sequential, unread }: ThreadMessageProps): Rea
}),
};
return (message: TMessage) =>
- parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateLanguage, useShowTranslated);
- }, [autoTranslateLanguage, katex, showColors, useShowTranslated]);
+ parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateOptions);
+ }, [katex, showColors, autoTranslateOptions]);
const normalizedMessage = useMemo(() => normalizeMessage(message), [message, normalizeMessage]);
diff --git a/apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx b/apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx
index 34e9033dad4e..4e5c1539838f 100644
--- a/apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx
+++ b/apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx
@@ -10,12 +10,14 @@ import {
ThreadMessageBody,
ThreadMessageUnfollow,
CheckBox,
+ MessageStatusIndicatorItem,
} from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';
import { MessageTypes } from '../../../../app/ui-utils/client';
+import { useShowTranslated } from '../../../views/room/MessageList/contexts/MessageListContext';
import {
useIsSelecting,
useToggleSelect,
@@ -24,6 +26,7 @@ import {
} from '../../../views/room/MessageList/contexts/SelectedMessagesContext';
import { useMessageBody } from '../../../views/room/MessageList/hooks/useMessageBody';
import { useParentMessage } from '../../../views/room/MessageList/hooks/useParentMessage';
+import { isParsedMessage } from '../../../views/room/MessageList/lib/isParsedMessage';
import { useMessageActions } from '../../../views/room/contexts/MessageContext';
import UserAvatar from '../../avatar/UserAvatar';
import ThreadMessagePreviewBody from './threadPreview/ThreadMessagePreviewBody';
@@ -38,7 +41,8 @@ const ThreadMessagePreview = ({ message, sequential, ...props }: ThreadMessagePr
actions: { openThread },
} = useMessageActions();
const parentMessage = useParentMessage(message.tmid);
- const body = useMessageBody(parentMessage.data);
+
+ const translated = useShowTranslated(message);
const t = useTranslation();
const isSelecting = useIsSelecting();
@@ -47,6 +51,9 @@ const ThreadMessagePreview = ({ message, sequential, ...props }: ThreadMessagePr
useCountSelected();
const messageType = parentMessage.isSuccess ? MessageTypes.getType(parentMessage.data) : null;
+ const messageBody = useMessageBody(parentMessage.data, message.rid);
+
+ const previewMessage = isParsedMessage(messageBody) ? { md: messageBody } : { msg: messageBody };
return (
@@ -62,7 +69,13 @@ const ThreadMessagePreview = ({ message, sequential, ...props }: ThreadMessagePr
{(parentMessage.data as { ignored?: boolean })?.ignored ? (
t('Message_Ignored')
) : (
-
+
+ )}
+ {translated && (
+ <>
+ {' '}
+
+ >
)}
>
)}
@@ -80,7 +93,19 @@ const ThreadMessagePreview = ({ message, sequential, ...props }: ThreadMessagePr
- {(message as { ignored?: boolean }).ignored ? t('Message_Ignored') : }
+ {(message as { ignored?: boolean }).ignored ? (
+ t('Message_Ignored')
+ ) : (
+ <>
+
+ {translated && (
+ <>
+ {' '}
+
+ >
+ )}
+ >
+ )}
diff --git a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx
index 779475a6e585..9117e4a12778 100644
--- a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx
+++ b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx
@@ -7,7 +7,7 @@ import React, { memo } from 'react';
import { useUserData } from '../../../../hooks/useUserData';
import type { UserPresence } from '../../../../lib/presence';
-import { useTranslateAttachments, useMessageListShowReadReceipt } from '../../../../views/room/MessageList/contexts/MessageListContext';
+import { useMessageListShowReadReceipt } from '../../../../views/room/MessageList/contexts/MessageListContext';
import type { MessageWithMdEnforced } from '../../../../views/room/MessageList/lib/parseMessageTextToAstMarkdown';
import { useMessageActions, useMessageOembedIsEnabled, useMessageRunActionLink } from '../../../../views/room/contexts/MessageContext';
import MessageContentBody from '../../MessageContentBody';
@@ -47,8 +47,6 @@ const RoomMessageContent = ({ message, unread, all, mention }: RoomMessageConten
const isEncryptedMessage = isE2EEMessage(message);
- const messageAttachments = useTranslateAttachments({ message });
-
return (
<>
{!message.blocks?.length && !!message.md?.length && (
@@ -62,7 +60,7 @@ const RoomMessageContent = ({ message, unread, all, mention }: RoomMessageConten
{message.blocks && }
- {!!messageAttachments.length && }
+ {!!message?.attachments?.length && }
{oembedIsEnabled && !!message.urls?.length && }
diff --git a/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx b/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx
index cdd6f12674a4..fb1eb021cdb9 100644
--- a/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx
+++ b/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx
@@ -7,7 +7,7 @@ import React, { memo } from 'react';
import { useUserData } from '../../../../hooks/useUserData';
import type { UserPresence } from '../../../../lib/presence';
-import { useMessageListShowReadReceipt, useTranslateAttachments } from '../../../../views/room/MessageList/contexts/MessageListContext';
+import { useMessageListShowReadReceipt } from '../../../../views/room/MessageList/contexts/MessageListContext';
import type { MessageWithMdEnforced } from '../../../../views/room/MessageList/lib/parseMessageTextToAstMarkdown';
import { useMessageActions, useMessageOembedIsEnabled, useMessageRunActionLink } from '../../../../views/room/contexts/MessageContext';
import MessageContentBody from '../../MessageContentBody';
@@ -40,8 +40,6 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem
const isEncryptedMessage = isE2EEMessage(message);
- const messageAttachments = useTranslateAttachments({ message });
-
return (
<>
{!message.blocks?.length && !!message.md?.length && (
@@ -55,7 +53,7 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem
{message.blocks && }
- {messageAttachments && }
+ {message.attachments && }
{oembedIsEnabled && !!message.urls?.length && }
diff --git a/apps/meteor/client/components/message/variants/threadPreview/ThreadMessagePreviewBody.tsx b/apps/meteor/client/components/message/variants/threadPreview/ThreadMessagePreviewBody.tsx
index c4f036631492..dcf4ffc5421e 100644
--- a/apps/meteor/client/components/message/variants/threadPreview/ThreadMessagePreviewBody.tsx
+++ b/apps/meteor/client/components/message/variants/threadPreview/ThreadMessagePreviewBody.tsx
@@ -5,8 +5,6 @@ import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';
-import { parseMessageTextToAstMarkdown } from '../../../../views/room/MessageList/lib/parseMessageTextToAstMarkdown';
-
type ThreadMessagePreviewBodyProps = {
message: IMessage;
};
@@ -15,18 +13,16 @@ const ThreadMessagePreviewBody = ({ message }: ThreadMessagePreviewBodyProps): R
const t = useTranslation();
const isEncryptedMessage = isE2EEMessage(message);
- const parsedMessage = parseMessageTextToAstMarkdown(message, { colors: true, emoticons: true });
-
const getMessage = () => {
if (!isEncryptedMessage || message.e2e === 'done') {
- return parsedMessage.md ? : <>{parsedMessage.msg}>;
+ return message.md ? : <>{message.msg}>;
}
if (isEncryptedMessage && message.e2e === 'pending') {
return <>{t('E2E_message_encrypted_placeholder')}>;
}
- return <>{parsedMessage.msg}>;
+ return <>{message.msg}>;
};
return getMessage();
diff --git a/apps/meteor/client/lib/utils/messageArgs.ts b/apps/meteor/client/lib/utils/messageArgs.ts
index 83248369c99a..0a92a6fa8adc 100644
--- a/apps/meteor/client/lib/utils/messageArgs.ts
+++ b/apps/meteor/client/lib/utils/messageArgs.ts
@@ -1,10 +1,10 @@
-import type { IMessage, IRoom, ISubscription, IUser, SettingValue } from '@rocket.chat/core-typings';
+import type { IRoom, ISubscription, ITranslatedMessage, IUser, SettingValue } from '@rocket.chat/core-typings';
export const messageArgs = (
context: any,
): {
context?: 'threads' | 'mentions';
- msg: IMessage;
+ msg: ITranslatedMessage;
u: IUser;
room: IRoom;
settings: Record;
diff --git a/apps/meteor/client/views/room/MessageList/MessageList.tsx b/apps/meteor/client/views/room/MessageList/MessageList.tsx
index 663b72229594..71bd3119adc5 100644
--- a/apps/meteor/client/views/room/MessageList/MessageList.tsx
+++ b/apps/meteor/client/views/room/MessageList/MessageList.tsx
@@ -1,4 +1,4 @@
-import type { IRoom } from '@rocket.chat/core-typings';
+import type { IRoom, IThreadMessage } from '@rocket.chat/core-typings';
import { isThreadMessage } from '@rocket.chat/core-typings';
import { MessageDivider } from '@rocket.chat/fuselage';
import { useSetting, useTranslation } from '@rocket.chat/ui-contexts';
@@ -71,7 +71,7 @@ export const MessageList = ({ rid }: MessageListProps): ReactElement => {
data-unread={firstUnread}
data-sequential={sequential}
sequential={shouldShowAsSequential}
- message={message}
+ message={message as IThreadMessage}
/>
)}
diff --git a/apps/meteor/client/views/room/MessageList/contexts/MessageListContext.tsx b/apps/meteor/client/views/room/MessageList/contexts/MessageListContext.tsx
index bc0fe3dd776f..388493451d72 100644
--- a/apps/meteor/client/views/room/MessageList/contexts/MessageListContext.tsx
+++ b/apps/meteor/client/views/room/MessageList/contexts/MessageListContext.tsx
@@ -1,10 +1,8 @@
-import type { IMessage, MessageAttachment } from '@rocket.chat/core-typings';
+import type { IMessage } from '@rocket.chat/core-typings';
import { createContext, useContext } from 'react';
export type MessageListContextValue = {
- useShowTranslated: ({ message }: { message: IMessage }) => boolean;
- useTranslateProvider: ({ message }: { message: IMessage }) => string | boolean;
- useTranslateAttachments: ({ message }: { message: IMessage }) => MessageAttachment[];
+ useShowTranslated: (message: IMessage) => boolean;
useShowStarred: ({ message }: { message: IMessage }) => boolean;
useShowFollowing: ({ message }: { message: IMessage }) => boolean;
useMessageDateFormatter: () => (date: Date) => string;
@@ -32,8 +30,6 @@ export type MessageListContextValue = {
export const MessageListContext = createContext({
useShowTranslated: () => false,
- useTranslateProvider: () => false,
- useTranslateAttachments: () => [],
useShowStarred: () => false,
useShowFollowing: () => false,
useUserHasReacted: () => (): boolean => false,
@@ -56,10 +52,6 @@ export const MessageListContext = createContext({
export const useShowTranslated: MessageListContextValue['useShowTranslated'] = (...args) =>
useContext(MessageListContext).useShowTranslated(...args);
-export const useTranslateProvider: MessageListContextValue['useTranslateProvider'] = (...args) =>
- useContext(MessageListContext).useTranslateProvider(...args);
-export const useTranslateAttachments: MessageListContextValue['useTranslateAttachments'] = (...args) =>
- useContext(MessageListContext).useTranslateAttachments(...args);
export const useShowStarred: MessageListContextValue['useShowStarred'] = (...args) =>
useContext(MessageListContext).useShowStarred(...args);
export const useShowFollowing: MessageListContextValue['useShowFollowing'] = (...args) =>
diff --git a/apps/meteor/client/views/room/MessageList/hooks/useAutoTranslate.ts b/apps/meteor/client/views/room/MessageList/hooks/useAutoTranslate.ts
new file mode 100644
index 000000000000..3786f45de75a
--- /dev/null
+++ b/apps/meteor/client/views/room/MessageList/hooks/useAutoTranslate.ts
@@ -0,0 +1,38 @@
+import type { IMessage, ISubscription, ITranslatedMessage } from '@rocket.chat/core-typings';
+import { useSetting } from '@rocket.chat/ui-contexts';
+import { useCallback, useMemo } from 'react';
+
+import { hasTranslationLanguageInAttachments, hasTranslationLanguageInMessage } from '../lib/autoTranslate';
+import { isOwnUserMessage } from '../lib/isOwnUserMessage';
+
+export type AutoTranslateOptions = {
+ autoTranslateEnabled: boolean;
+ autoTranslateLanguage?: string;
+ showAutoTranslate: (message: IMessage & Partial) => boolean;
+};
+
+export const useAutoTranslate = (subscription?: ISubscription): AutoTranslateOptions => {
+ const autoTranslateSettingEnabled = Boolean(useSetting('AutoTranslate_Enabled'));
+ const autoTranslateEnabled = Boolean(autoTranslateSettingEnabled && subscription?.autoTranslateLanguage && subscription?.autoTranslate);
+ const autoTranslateLanguage = autoTranslateEnabled ? subscription?.autoTranslateLanguage : undefined;
+
+ const showAutoTranslate = useCallback(
+ (message: IMessage): boolean => {
+ if (!autoTranslateEnabled || !autoTranslateLanguage) {
+ return false;
+ }
+
+ return (
+ !isOwnUserMessage(message, subscription) &&
+ !(message as { autoTranslateShowInverse?: boolean }).autoTranslateShowInverse &&
+ (hasTranslationLanguageInMessage(message, autoTranslateLanguage) ||
+ hasTranslationLanguageInAttachments(message.attachments, autoTranslateLanguage))
+ );
+ },
+ [subscription, autoTranslateEnabled, autoTranslateLanguage],
+ );
+
+ return useMemo(() => {
+ return { autoTranslateEnabled, autoTranslateLanguage, showAutoTranslate };
+ }, [autoTranslateEnabled, autoTranslateLanguage, showAutoTranslate]);
+};
diff --git a/apps/meteor/client/views/room/MessageList/hooks/useAutotranslateLanguage.ts b/apps/meteor/client/views/room/MessageList/hooks/useAutotranslateLanguage.ts
deleted file mode 100644
index 08a4a63d27ab..000000000000
--- a/apps/meteor/client/views/room/MessageList/hooks/useAutotranslateLanguage.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { IRoom } from '@rocket.chat/core-typings';
-import { useCallback } from 'react';
-
-import { AutoTranslate } from '../../../../../app/autotranslate/client/lib/autotranslate';
-import { useReactiveValue } from '../../../../hooks/useReactiveValue';
-
-export const useAutotranslateLanguage = (rid: IRoom['_id']): string | undefined =>
- useReactiveValue(useCallback(() => AutoTranslate.getLanguage(rid), [rid]));
diff --git a/apps/meteor/client/views/room/MessageList/hooks/useKatex.ts b/apps/meteor/client/views/room/MessageList/hooks/useKatex.ts
new file mode 100644
index 000000000000..b652bf283d54
--- /dev/null
+++ b/apps/meteor/client/views/room/MessageList/hooks/useKatex.ts
@@ -0,0 +1,17 @@
+import { useSetting } from '@rocket.chat/ui-contexts';
+
+export const useKatex = (): {
+ katexEnabled: boolean;
+ katexDollarSyntaxEnabled: boolean;
+ katexParenthesisSyntaxEnabled: boolean;
+} => {
+ const katexEnabled = Boolean(useSetting('Katex_Enabled'));
+ const katexDollarSyntaxEnabled = Boolean(useSetting('Katex_Dollar_Syntax')) && katexEnabled;
+ const katexParenthesisSyntaxEnabled = Boolean(useSetting('Katex_Parenthesis_Syntax')) && katexEnabled;
+
+ return {
+ katexEnabled,
+ katexDollarSyntaxEnabled,
+ katexParenthesisSyntaxEnabled,
+ };
+};
diff --git a/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx b/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
index 55c98ec5352b..12d7ed5e87a1 100644
--- a/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
+++ b/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
@@ -1,14 +1,35 @@
import type { IMessage } from '@rocket.chat/core-typings';
+import type { Options, Root } from '@rocket.chat/message-parser';
+import { useUserSubscription } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
-export const useMessageBody = ({ attachments, msg }: Partial> = {}): string =>
- useMemo(() => {
- if (msg) {
- return msg;
+import { parseMessageTextToAstMarkdown } from '../lib/parseMessageTextToAstMarkdown';
+import { useAutoTranslate } from './useAutoTranslate';
+
+export const useMessageBody = (message: IMessage | undefined, rid: string): string | Root => {
+ const subscription = useUserSubscription(rid);
+ const autoTranslateOptions = useAutoTranslate(subscription);
+ return useMemo(() => {
+ if (!message) {
+ return '';
+ }
+
+ if (message.md) {
+ const parseOptions: Options = {
+ emoticons: true,
+ };
+
+ const messageWithMd = parseMessageTextToAstMarkdown(message, parseOptions, autoTranslateOptions);
+
+ return messageWithMd.md;
+ }
+
+ if (message.msg) {
+ return message.msg;
}
- if (attachments) {
- const attachment = attachments.find((attachment) => attachment.title || attachment.description);
+ if (message.attachments) {
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
if (attachment?.description) {
return attachment.description;
@@ -20,4 +41,5 @@ export const useMessageBody = ({ attachments, msg }: Partial {
- const { autoTranslateLanguage, katex, showColors, useShowTranslated } = useMessageListContext();
+ const { katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled } = useKatex();
+ const subscription = useUserSubscription(rid);
+
+ const autoTranslateOptions = useAutoTranslate(subscription);
+ const showColors = Boolean(useSetting('HexColorPreview_Enabled'));
const hideSysMes = useSetting('Hide_System_Messages');
const hideSysMessagesStable = useStableArray(Array.isArray(hideSysMes) ? hideSysMes : []);
@@ -26,16 +31,16 @@ export const useMessages = ({ rid }: { rid: IRoom['_id'] }): MessageWithMdEnforc
const parseOptions = {
colors: showColors,
emoticons: true,
- ...(Boolean(katex) && {
+ ...(katexEnabled && {
katex: {
- dollarSyntax: katex?.dollarSyntaxEnabled,
- parenthesisSyntax: katex?.parenthesisSyntaxEnabled,
+ dollarSyntax: katexDollarSyntaxEnabled,
+ parenthesisSyntax: katexParenthesisSyntaxEnabled,
},
}),
};
return (message: IMessage): MessageWithMdEnforced =>
- parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateLanguage, useShowTranslated);
- }, [autoTranslateLanguage, katex, showColors, useShowTranslated]);
+ parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateOptions);
+ }, [showColors, katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled, autoTranslateOptions]);
const query: Mongo.Query = useMemo(
() => ({
@@ -48,6 +53,6 @@ export const useMessages = ({ rid }: { rid: IRoom['_id'] }): MessageWithMdEnforc
);
return useReactiveValue(
- useCallback(() => Messages.find(query, options).fetch().map(normalizeMessage), [query, normalizeMessage]),
+ useCallback(() => ChatMessage.find(query, options).fetch().map(normalizeMessage), [query, normalizeMessage]),
);
};
diff --git a/apps/meteor/client/views/room/MessageList/lib/autoTranslate.ts b/apps/meteor/client/views/room/MessageList/lib/autoTranslate.ts
new file mode 100644
index 000000000000..a98ea330686b
--- /dev/null
+++ b/apps/meteor/client/views/room/MessageList/lib/autoTranslate.ts
@@ -0,0 +1,8 @@
+import type { IMessage, MessageAttachment } from '@rocket.chat/core-typings';
+import { isTranslatedMessageAttachment, isTranslatedMessage } from '@rocket.chat/core-typings';
+
+export const hasTranslationLanguageInMessage = (message: IMessage, language: string): boolean =>
+ isTranslatedMessage(message) && Boolean(message.translations?.[language]);
+
+export const hasTranslationLanguageInAttachments = (attachments: MessageAttachment[] = [], language: string): boolean =>
+ isTranslatedMessageAttachment(attachments) && attachments?.some((attachment) => attachment?.translations?.[language]);
diff --git a/apps/meteor/client/views/room/MessageList/lib/parseMessageTextToAstMarkdown.ts b/apps/meteor/client/views/room/MessageList/lib/parseMessageTextToAstMarkdown.ts
index 0c9c976ca423..359cf6d0e06b 100644
--- a/apps/meteor/client/views/room/MessageList/lib/parseMessageTextToAstMarkdown.ts
+++ b/apps/meteor/client/views/room/MessageList/lib/parseMessageTextToAstMarkdown.ts
@@ -1,77 +1,99 @@
-import type { IMessage, MessageAttachment, MessageQuoteAttachment } from '@rocket.chat/core-typings';
-import { isE2EEMessage, isOTRMessage, isQuoteAttachment, isTranslatedMessage } from '@rocket.chat/core-typings';
+import type { IMessage, ITranslatedMessage, MessageAttachment, MessageQuoteAttachment } from '@rocket.chat/core-typings';
+import { isE2EEMessage, isOTRMessage, isQuoteAttachment, isTranslatedAttachment, isTranslatedMessage } from '@rocket.chat/core-typings';
import type { Options, Root } from '@rocket.chat/message-parser';
import { parse } from '@rocket.chat/message-parser';
+import type { AutoTranslateOptions } from '../hooks/useAutoTranslate';
import { isParsedMessage } from './isParsedMessage';
type WithRequiredProperty = Omit & {
[Property in Key]-?: Type[Property];
};
-export type MessageWithMdEnforced = WithRequiredProperty;
-/*
+export type MessageWithMdEnforced = IMessage & Partial> =
+ WithRequiredProperty;
+/**
* Removes null values for known properties values.
* Adds a property `md` to the message with the parsed message if is not provided.
* if has `attachments` property, but attachment is missing `md` property, it will be added.
* if translation is enabled and message contains `translations` property, it will be replaced by the parsed message.
* @param message The message to be parsed.
* @param parseOptions The options to be used in the parser.
- * @param autoTranslateLanguage The language to be used in the parser.
- * @param showTranslatedMessage function that evaluates if message should be translated.
+ * @param autoTranslateOptions The auto translate options to be used in the parser.
* @returns message normalized.
*/
-
-export const parseMessageTextToAstMarkdown = (
+export const parseMessageTextToAstMarkdown = <
+ TMessage extends IMessage & Partial = IMessage & Partial,
+>(
message: TMessage,
parseOptions: Options,
- autoTranslateLanguage?: string,
- showTranslated?: ({ message }: { message: IMessage }) => boolean,
-): MessageWithMdEnforced => {
+ autoTranslateOptions: AutoTranslateOptions,
+): MessageWithMdEnforced => {
const msg = removePossibleNullMessageValues(message);
- const translations = autoTranslateLanguage && showTranslated && isTranslatedMessage(msg) && msg.translations;
- const translated = autoTranslateLanguage && showTranslated?.({ message });
+ const { showAutoTranslate, autoTranslateLanguage } = autoTranslateOptions;
+ const translations = autoTranslateLanguage && isTranslatedMessage(msg) && msg.translations;
+ const translated = showAutoTranslate(message);
const text = (translated && translations && translations[autoTranslateLanguage]) || msg.msg;
return {
...msg,
md:
- isE2EEMessage(message) || isOTRMessage(message)
+ isE2EEMessage(message) || isOTRMessage(message) || translated
? textToMessageToken(text, parseOptions)
: msg.md ?? textToMessageToken(text, parseOptions),
- ...(msg.attachments && { attachments: parseMessageAttachments(msg.attachments, parseOptions) }),
- } as MessageWithMdEnforced;
+ ...(msg.attachments && {
+ attachments: parseMessageAttachments(msg.attachments, parseOptions, { autoTranslateLanguage, translated }),
+ }),
+ };
};
-const parseMessageQuoteAttachment = (quote: T, parseOptions: Options): T => {
+export const parseMessageQuoteAttachment = (
+ quote: T,
+ parseOptions: Options,
+ autoTranslateOptions: { autoTranslateLanguage?: string; translated: boolean },
+): T => {
+ const { translated, autoTranslateLanguage } = autoTranslateOptions;
if (quote.attachments && quote.attachments?.length > 0) {
- quote.attachments = quote.attachments.map((attachment) => parseMessageQuoteAttachment(attachment, parseOptions));
+ quote.attachments = quote.attachments.map((attachment) => parseMessageQuoteAttachment(attachment, parseOptions, autoTranslateOptions));
}
- return { ...quote, md: quote.md ?? textToMessageToken(quote.text, parseOptions) };
+ const text = (isTranslatedAttachment(quote) && autoTranslateLanguage && quote?.translations?.[autoTranslateLanguage]) || quote.text || '';
+
+ return {
+ ...quote,
+ md: translated ? textToMessageToken(text, parseOptions) : quote.md ?? textToMessageToken(text, parseOptions),
+ };
};
-const parseMessageAttachments = (attachments: T[], parseOptions: Options): T[] => {
- if (attachments.length === 0) {
- return attachments;
- }
+export const parseMessageAttachments = (
+ attachments: T[],
+ parseOptions: Options,
+ autoTranslateOptions: { autoTranslateLanguage?: string; translated: boolean },
+): T[] =>
+ attachments.map((attachment) => {
+ const { translated, autoTranslateLanguage } = autoTranslateOptions;
+ if (!attachment.text && !attachment.description) {
+ return attachment;
+ }
- return attachments.map((attachment) => {
if (isQuoteAttachment(attachment) && attachment.attachments) {
- attachment.attachments = attachment.attachments.map((quoteAttachment) => parseMessageQuoteAttachment(quoteAttachment, parseOptions));
+ attachment.attachments = attachment.attachments.map((quoteAttachment) =>
+ parseMessageQuoteAttachment(quoteAttachment, parseOptions, autoTranslateOptions),
+ );
}
- if (!attachment.text) {
- return attachment;
- }
+ const text =
+ (isTranslatedAttachment(attachment) && autoTranslateLanguage && attachment?.translations?.[autoTranslateLanguage]) ||
+ attachment.text ||
+ attachment.description ||
+ '';
return {
...attachment,
- md: attachment.md ?? textToMessageToken(attachment.text, parseOptions),
+ md: translated ? textToMessageToken(text, parseOptions) : attachment.md ?? textToMessageToken(text, parseOptions),
};
});
-};
const isNotNullOrUndefined = (value: unknown): boolean => value !== null && value !== undefined;
@@ -110,6 +132,9 @@ const textToMessageToken = (textOrRoot: string | Root, parseOptions: Options): R
if (isParsedMessage(textOrRoot)) {
return textOrRoot;
}
+ const parsedMessage = parse(textOrRoot, parseOptions);
+
+ const parsedMessageCleaned = parsedMessage[0].type !== 'LINE_BREAK' ? parsedMessage : (parsedMessage.slice(1) as Root);
- return parse(textOrRoot, parseOptions);
+ return parsedMessageCleaned;
};
diff --git a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx
index 707348392db5..e09b237cf6a3 100644
--- a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx
+++ b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx
@@ -1,17 +1,17 @@
-import type { IMessage, MessageAttachment } from '@rocket.chat/core-typings';
-import { isTranslatedMessage, isMessageReactionsNormalized, isThreadMainMessage } from '@rocket.chat/core-typings';
+import type { IMessage } from '@rocket.chat/core-typings';
+import { isMessageReactionsNormalized, isThreadMainMessage } from '@rocket.chat/core-typings';
import { useLayout, useUser, useUserPreference, useSetting, useEndpoint } from '@rocket.chat/ui-contexts';
import type { VFC, ReactNode } from 'react';
import React, { useMemo, memo } from 'react';
-import { AutoTranslate } from '../../../../../app/autotranslate/client';
import { EmojiPicker } from '../../../../../app/emoji/client';
import { getRegexHighlight, getRegexHighlightUrl } from '../../../../../app/highlight-words/client/helper';
import { useRoom, useRoomSubscription } from '../../contexts/RoomContext';
import ToolboxProvider from '../../providers/ToolboxProvider';
import type { MessageListContextValue } from '../contexts/MessageListContext';
import { MessageListContext } from '../contexts/MessageListContext';
-import { useAutotranslateLanguage } from '../hooks/useAutotranslateLanguage';
+import { useAutoTranslate } from '../hooks/useAutoTranslate';
+import { useKatex } from '../hooks/useKatex';
type MessageListProviderProps = {
children: ReactNode;
@@ -34,10 +34,6 @@ const MessageListProvider: VFC = ({ children }) => {
const showRealName = Boolean(useSetting('UI_Use_Real_Name'));
const showReadReceipt = Boolean(useSetting('Message_Read_Receipt_Enabled'));
- const autoTranslateEnabled = useSetting('AutoTranslate_Enabled');
- const katexEnabled = Boolean(useSetting('Katex_Enabled'));
- const katexDollarSyntaxEnabled = Boolean(useSetting('Katex_Dollar_Syntax'));
- const katexParenthesisSyntaxEnabled = Boolean(useSetting('Katex_Parenthesis_Syntax'));
const showColors = useSetting('HexColorPreview_Enabled') as boolean;
const displayRolesGlobal = Boolean(useSetting('UI_DisplayRoles'));
@@ -46,7 +42,8 @@ const MessageListProvider: VFC = ({ children }) => {
const showUsername = Boolean(!useUserPreference('hideUsernames') && !isMobile);
const highlights = useUserPreference('highlights');
- const autoTranslateLanguage = useAutotranslateLanguage(room._id);
+ const { showAutoTranslate, autoTranslateLanguage } = useAutoTranslate(subscription);
+ const { katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled } = useKatex();
const hasSubscription = Boolean(subscription);
@@ -85,30 +82,7 @@ const MessageListProvider: VFC = ({ children }) => {
? ({ message }): boolean => Boolean(message.replies && message.replies.indexOf(uid) > -1 && !isThreadMainMessage(message))
: (): boolean => false,
autoTranslateLanguage,
- useShowTranslated:
- uid && autoTranslateEnabled && hasSubscription && autoTranslateLanguage
- ? ({ message }): boolean =>
- Boolean(message.u) &&
- message.u?._id !== uid &&
- isTranslatedMessage(message) &&
- Boolean(message.translations[autoTranslateLanguage]) &&
- !message.autoTranslateShowInverse
- : (): boolean => false,
- useTranslateProvider:
- autoTranslateEnabled && autoTranslateLanguage
- ? ({ message }): string | boolean =>
- isTranslatedMessage(message) && AutoTranslate.providersMetadata[message.translationProvider]?.displayName
- : (): boolean => false,
- useTranslateAttachments:
- uid && autoTranslateEnabled && hasSubscription && autoTranslateLanguage
- ? ({ message }): MessageAttachment[] =>
- (isTranslatedMessage(message) &&
- message.u?._id !== uid &&
- message.attachments &&
- AutoTranslate.translateAttachments(message.attachments, autoTranslateLanguage, !!message.autoTranslateShowInverse)) ||
- message.attachments ||
- []
- : ({ message }): MessageAttachment[] => message.attachments || [],
+ useShowTranslated: showAutoTranslate,
useShowStarred: hasSubscription
? ({ message }): boolean => Boolean(Array.isArray(message.starred) && message.starred.find((star) => star._id === uid))
: (): boolean => false,
@@ -153,7 +127,7 @@ const MessageListProvider: VFC = ({ children }) => {
[
username,
uid,
- autoTranslateEnabled,
+ showAutoTranslate,
hasSubscription,
autoTranslateLanguage,
showRoles,
diff --git a/apps/meteor/tests/unit/client/views/room/MessageList/hooks/useAutoTranslate.spec.ts b/apps/meteor/tests/unit/client/views/room/MessageList/hooks/useAutoTranslate.spec.ts
new file mode 100644
index 000000000000..be5d21e653c8
--- /dev/null
+++ b/apps/meteor/tests/unit/client/views/room/MessageList/hooks/useAutoTranslate.spec.ts
@@ -0,0 +1,82 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { expect } from 'chai';
+import proxyquire from 'proxyquire';
+
+const COMPONENT_PATH = '../../../../../../../client/views/room/MessageList/hooks/useAutoTranslate';
+const defaultConfig = {
+ '@rocket.chat/ui-contexts': {
+ useSetting: () => true,
+ },
+};
+
+describe('room/MessageList/hooks/useAutoTranslate', () => {
+ it('should return enabled false and undefined language if no subscription and setting disabled', () => {
+ const { useAutoTranslate } = proxyquire.load(COMPONENT_PATH, {
+ ...defaultConfig,
+ '@rocket.chat/ui-contexts': {
+ useSetting: () => false,
+ },
+ });
+
+ const { result } = renderHook(() => useAutoTranslate());
+
+ expect(result.current.autoTranslateEnabled).to.be.equal(false);
+ expect(result.current.autoTranslateLanguage).to.be.undefined;
+
+ expect(result.current.showAutoTranslate({ u: { _id: 2 } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 1 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 2 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ });
+
+ it('should return enabled false and undefined language if no subscription', () => {
+ const { useAutoTranslate } = proxyquire.load(COMPONENT_PATH, defaultConfig);
+
+ const { result } = renderHook(() => useAutoTranslate());
+
+ expect(result.current.autoTranslateEnabled).to.be.equal(false);
+ expect(result.current.autoTranslateLanguage).to.be.undefined;
+
+ expect(result.current.showAutoTranslate({ u: { _id: 2 } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 1 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 2 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ });
+
+ it('should return enabled true and the auto translate language if has subscription', () => {
+ const { useAutoTranslate } = proxyquire.load(COMPONENT_PATH, defaultConfig);
+
+ const { result } = renderHook(() => useAutoTranslate({ autoTranslate: true, autoTranslateLanguage: 'lang', u: { _id: 1 } }));
+
+ expect(result.current.autoTranslateEnabled).to.be.equal(true);
+ expect(result.current.autoTranslateLanguage).to.be.equal('lang');
+
+ expect(result.current.showAutoTranslate({ u: { _id: 2 } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 1 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 2 }, translations: { lang: 'translated' } })).to.be.equal(true);
+ });
+
+ it('should return enabled false if no auto translate language', () => {
+ const { useAutoTranslate } = proxyquire.load(COMPONENT_PATH, defaultConfig);
+
+ const { result } = renderHook(() => useAutoTranslate({ autoTranslate: true, autoTranslateLanguage: undefined, u: { _id: 1 } }));
+
+ expect(result.current.autoTranslateEnabled).to.be.equal(false);
+ expect(result.current.autoTranslateLanguage).to.be.equal(undefined);
+
+ expect(result.current.showAutoTranslate({ u: { _id: 2 } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 1 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 2 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ });
+
+ it('should return enabled false and language undefined if auto translate is false and has auto translate language', () => {
+ const { useAutoTranslate } = proxyquire.load(COMPONENT_PATH, defaultConfig);
+
+ const { result } = renderHook(() => useAutoTranslate({ autoTranslate: false, autoTranslateLanguage: 'lang', u: { _id: 1 } }));
+
+ expect(result.current.autoTranslateEnabled).to.be.equal(false);
+ expect(result.current.autoTranslateLanguage).to.be.equal(undefined);
+
+ expect(result.current.showAutoTranslate({ u: { _id: 2 } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 1 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ expect(result.current.showAutoTranslate({ u: { _id: 2 }, translations: { lang: 'translated' } })).to.be.equal(false);
+ });
+});
diff --git a/apps/meteor/tests/unit/client/views/room/MessageList/hooks/useKatex.spec.ts b/apps/meteor/tests/unit/client/views/room/MessageList/hooks/useKatex.spec.ts
new file mode 100644
index 000000000000..1739f070e32a
--- /dev/null
+++ b/apps/meteor/tests/unit/client/views/room/MessageList/hooks/useKatex.spec.ts
@@ -0,0 +1,67 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { expect } from 'chai';
+import proxyquire from 'proxyquire';
+
+const COMPONENT_PATH = '../../../../../../../client/views/room/MessageList/hooks/useKatex';
+const defaultConfig = {
+ '@rocket.chat/ui-contexts': {
+ useSetting: () => true,
+ },
+};
+
+describe('room/MessageList/hooks/useKatex', () => {
+ it('should return enabled true dollar syntax true and parenthesis syntax true if all settings is enabled', () => {
+ const { useKatex } = proxyquire.load(COMPONENT_PATH, defaultConfig);
+
+ const { result } = renderHook(() => useKatex());
+
+ expect(result.current.katexEnabled).to.be.equal(true);
+ expect(result.current.katexDollarSyntaxEnabled).to.be.equal(true);
+ expect(result.current.katexParenthesisSyntaxEnabled).to.be.equal(true);
+ });
+
+ it('should return enabled false dollar syntax false and parenthesis syntax false if all settings is disabled', () => {
+ const { useKatex } = proxyquire.load(COMPONENT_PATH, {
+ ...defaultConfig,
+ '@rocket.chat/ui-contexts': {
+ useSetting: () => false,
+ },
+ });
+
+ const { result } = renderHook(() => useKatex());
+
+ expect(result.current.katexEnabled).to.be.equal(false);
+ expect(result.current.katexDollarSyntaxEnabled).to.be.equal(false);
+ expect(result.current.katexParenthesisSyntaxEnabled).to.be.equal(false);
+ });
+
+ it('should return enabled true dollar syntax false and parenthesis syntax false if Katex_Enabled settings is enable', () => {
+ const { useKatex } = proxyquire.load(COMPONENT_PATH, {
+ ...defaultConfig,
+ '@rocket.chat/ui-contexts': {
+ useSetting: (str: string) => str === 'Katex_Enabled',
+ },
+ });
+
+ const { result } = renderHook(() => useKatex());
+
+ expect(result.current.katexEnabled).to.be.equal(true);
+ expect(result.current.katexDollarSyntaxEnabled).to.be.equal(false);
+ expect(result.current.katexParenthesisSyntaxEnabled).to.be.equal(false);
+ });
+
+ it('should return enabled false dollar syntax false and parenthesis syntax false if DollarSyntaxEnabled and ParenthesisSyntaxEnabled settings is enable', () => {
+ const { useKatex } = proxyquire.load(COMPONENT_PATH, {
+ ...defaultConfig,
+ '@rocket.chat/ui-contexts': {
+ useSetting: (str: string) => str === 'DollarSyntaxEnabled' || str === 'ParenthesisSyntaxEnabled',
+ },
+ });
+
+ const { result } = renderHook(() => useKatex());
+
+ expect(result.current.katexEnabled).to.be.equal(false);
+ expect(result.current.katexDollarSyntaxEnabled).to.be.equal(false);
+ expect(result.current.katexParenthesisSyntaxEnabled).to.be.equal(false);
+ });
+});
diff --git a/apps/meteor/tests/unit/client/views/room/MessageList/lib/autoTranslate.spec.ts b/apps/meteor/tests/unit/client/views/room/MessageList/lib/autoTranslate.spec.ts
new file mode 100644
index 000000000000..bbf6f10cc7ef
--- /dev/null
+++ b/apps/meteor/tests/unit/client/views/room/MessageList/lib/autoTranslate.spec.ts
@@ -0,0 +1,43 @@
+import type { IMessage, MessageAttachment } from '@rocket.chat/core-typings';
+import { expect } from 'chai';
+
+import {
+ hasTranslationLanguageInAttachments,
+ hasTranslationLanguageInMessage,
+} from '../../../../../../../client/views/room/MessageList/lib/autoTranslate';
+
+describe('autoTranslate', () => {
+ describe('hasTranslationLanguageInMessage', () => {
+ const testCases = [
+ [{}, '', false],
+ [{ translations: { en: 'bah' } }, '', false],
+ [{ translations: { en: 'bah' } }, 'pt', false],
+ [{ translations: { en: 'bah' } }, 'en', true],
+ ] as const;
+
+ testCases.forEach(([message, language, expectedResult]) => {
+ it(`should return ${JSON.stringify(expectedResult)} for ${JSON.stringify(message)} with ${JSON.stringify(language)}`, () => {
+ const result = hasTranslationLanguageInMessage(message as unknown as IMessage, language);
+ expect(result).to.be.equal(expectedResult);
+ });
+ });
+ });
+
+ describe('hasTranslationLanguageInAttachments', () => {
+ const testCases = [
+ [[{}], '', false],
+ [undefined, '', false],
+ [[{ translations: { en: 'bah' } }], '', false],
+ [[{ translations: { en: 'bah' } }], 'pt', false],
+ [[{ translations: { en: 'bah' } }], 'pt', false],
+ [[{ translations: { en: 'bah' } }], 'en', true],
+ ] as const;
+
+ testCases.forEach(([attachment, language, expectedResult]) => {
+ it(`should return ${JSON.stringify(expectedResult)} for ${JSON.stringify(attachment)} with ${JSON.stringify(language)}`, () => {
+ const result = hasTranslationLanguageInAttachments(attachment as unknown as MessageAttachment[], language);
+ expect(result).to.be.equal(expectedResult);
+ });
+ });
+ });
+});
diff --git a/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessage.spec.ts b/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessage.spec.ts
index 35225bcb4a9c..756765fc42bf 100644
--- a/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessage.spec.ts
+++ b/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessage.spec.ts
@@ -1,6 +1,6 @@
/* eslint-env mocha */
import type { Options, Root } from '@rocket.chat/message-parser';
-import type { IMessage } from '@rocket.chat/core-typings';
+import type { IMessage, ITranslatedMessage } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { parseMessageTextToAstMarkdown } from '../../../../../../../client/views/room/MessageList/lib/parseMessageTextToAstMarkdown';
@@ -124,13 +124,193 @@ const baseMessage: IMessage = {
urls: [],
};
+const autoTranslateOptions = {
+ autoTranslateEnabled: false,
+ showAutoTranslate: () => false,
+};
+
+const quoteMessage = {
+ author_name: 'authorName',
+ author_link: 'link',
+ author_icon: 'icon',
+ md: [],
+};
+
describe('parseMessage', () => {
it('should return md property populated if the message is parsed', () => {
- expect(parseMessageTextToAstMarkdown(baseMessage, parseOptions).md).to.deep.equal(messageParserTokenMessage);
+ expect(parseMessageTextToAstMarkdown(baseMessage, parseOptions, autoTranslateOptions).md).to.deep.equal(messageParserTokenMessage);
});
it('should return correct parsed md property populated and fail in comparison with different Root element', () => {
- expect(parseMessageTextToAstMarkdown(baseMessage, parseOptions).md).to.not.deep.equal(messageParserTokenMessageWithWrongData);
+ expect(parseMessageTextToAstMarkdown(baseMessage, parseOptions, autoTranslateOptions).md).to.not.deep.equal(
+ messageParserTokenMessageWithWrongData,
+ );
+ });
+
+ describe('translated', () => {
+ const translatedMessage: ITranslatedMessage = {
+ ...baseMessage,
+ msg: 'message not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'message translated',
+ },
+ };
+ const translatedMessageParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'message translated',
+ },
+ ],
+ },
+ ];
+
+ const enabledAutoTranslatedOptions = {
+ autoTranslateEnabled: true,
+ autoTranslateLanguage: 'en',
+ showAutoTranslate: () => true,
+ };
+ it('should return correct translated parsed md when translate is active', () => {
+ expect(parseMessageTextToAstMarkdown(translatedMessage, parseOptions, enabledAutoTranslatedOptions).md).to.deep.equal(
+ translatedMessageParsed,
+ );
+ });
+
+ it('should return correct attachment translated parsed md when translate is active', () => {
+ const attachmentTranslatedMessage = {
+ ...translatedMessage,
+ attachments: [
+ {
+ description: 'description',
+ translations: {
+ en: 'description translated',
+ },
+ },
+ ],
+ };
+ const attachmentTranslatedMessageParsed = {
+ ...translatedMessage,
+ md: translatedMessageParsed,
+ attachments: [
+ {
+ description: 'description',
+ translations: {
+ en: 'description translated',
+ },
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'description translated',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ expect(parseMessageTextToAstMarkdown(attachmentTranslatedMessage, parseOptions, enabledAutoTranslatedOptions)).to.deep.equal(
+ attachmentTranslatedMessageParsed,
+ );
+ });
+
+ it('should return correct attachment quote translated parsed md when translate is active', () => {
+ const attachmentTranslatedMessage = {
+ ...translatedMessage,
+ attachments: [
+ {
+ text: 'text',
+ translations: {
+ en: 'text translated',
+ },
+ },
+ ],
+ };
+ const attachmentTranslatedMessageParsed = {
+ ...translatedMessage,
+ md: translatedMessageParsed,
+ attachments: [
+ {
+ text: 'text',
+ translations: {
+ en: 'text translated',
+ },
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'text translated',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ expect(parseMessageTextToAstMarkdown(attachmentTranslatedMessage, parseOptions, enabledAutoTranslatedOptions)).to.deep.equal(
+ attachmentTranslatedMessageParsed,
+ );
+ });
+
+ it('should return correct multiple attachment quote translated parsed md when translate is active', () => {
+ const attachmentTranslatedMessage = {
+ ...translatedMessage,
+ attachments: [
+ {
+ text: 'text',
+ translations: {
+ en: 'text translated',
+ },
+ attachments: [{ ...quoteMessage, text: 'text level 2', translations: { en: 'text level 2 translated' } }],
+ },
+ ],
+ };
+ const attachmentTranslatedMessageParsed = {
+ ...translatedMessage,
+ md: translatedMessageParsed,
+ attachments: [
+ {
+ text: 'text',
+ translations: {
+ en: 'text translated',
+ },
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'text translated',
+ },
+ ],
+ },
+ ],
+ attachments: [
+ {
+ ...quoteMessage,
+ text: 'text level 2',
+ translations: {
+ en: 'text level 2 translated',
+ },
+ },
+ ],
+ },
+ ],
+ };
+
+ expect(parseMessageTextToAstMarkdown(attachmentTranslatedMessage, parseOptions, enabledAutoTranslatedOptions)).to.deep.equal(
+ attachmentTranslatedMessageParsed,
+ );
+ });
});
// TODO: Add more tests for each type of message and for each type of token
diff --git a/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageAttachments.spec.ts b/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageAttachments.spec.ts
new file mode 100644
index 000000000000..b1910210b80f
--- /dev/null
+++ b/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageAttachments.spec.ts
@@ -0,0 +1,236 @@
+/* eslint-env mocha */
+import type { Options, Root } from '@rocket.chat/message-parser';
+import { expect } from 'chai';
+
+import { parseMessageAttachments } from '../../../../../../../client/views/room/MessageList/lib/parseMessageTextToAstMarkdown';
+
+const parseOptions: Options = {
+ colors: true,
+ emoticons: true,
+ katex: {
+ dollarSyntax: true,
+ parenthesisSyntax: true,
+ },
+};
+
+const messageParserTokenMessage: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'message ',
+ },
+ {
+ type: 'BOLD',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'bold',
+ },
+ ],
+ },
+ {
+ type: 'PLAIN_TEXT',
+ value: ' ',
+ },
+ {
+ type: 'ITALIC',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'italic',
+ },
+ ],
+ },
+ {
+ type: 'PLAIN_TEXT',
+ value: ' and ',
+ },
+ {
+ type: 'STRIKE',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'strike',
+ },
+ ],
+ },
+ ],
+ },
+];
+
+const autoTranslateOptions = {
+ autoTranslateEnabled: false,
+ translated: false,
+};
+
+const attachmentMessage = [
+ {
+ description: 'message **bold** _italic_ and ~strike~',
+ md: messageParserTokenMessage,
+ },
+];
+
+describe('parseMessageAttachments', () => {
+ it('should return md property populated if the message is parsed', () => {
+ expect(parseMessageAttachments(attachmentMessage, parseOptions, autoTranslateOptions)[0].md).to.deep.equal(messageParserTokenMessage);
+ });
+
+ it('should return md property populated if the attachment is not parsed', () => {
+ expect(parseMessageAttachments([{ ...attachmentMessage[0], md: undefined }], parseOptions, autoTranslateOptions)[0].md).to.deep.equal(
+ messageParserTokenMessage,
+ );
+ });
+
+ describe('translated', () => {
+ const enabledAutoTranslatedOptions = {
+ translated: true,
+ autoTranslateLanguage: 'en',
+ };
+
+ it('should return correct attachment description translated parsed md when translate is active', () => {
+ const descriptionAttachment = [
+ {
+ ...attachmentMessage[0],
+ description: 'attachment not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'attachment translated',
+ },
+ },
+ ];
+ const descriptionAttachmentParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'attachment translated',
+ },
+ ],
+ },
+ ];
+
+ expect(parseMessageAttachments(descriptionAttachment, parseOptions, enabledAutoTranslatedOptions)[0].md).to.deep.equal(
+ descriptionAttachmentParsed,
+ );
+ });
+
+ it('should return correct attachment description parsed md when translate is active and auto translate language is undefined', () => {
+ const descriptionAttachment = [
+ {
+ ...attachmentMessage[0],
+ description: 'attachment not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'attachment translated',
+ },
+ },
+ ];
+ const descriptionAttachmentParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'attachment not translated',
+ },
+ ],
+ },
+ ];
+
+ expect(
+ parseMessageAttachments(descriptionAttachment, parseOptions, {
+ ...enabledAutoTranslatedOptions,
+ autoTranslateLanguage: undefined,
+ })[0].md,
+ ).to.deep.equal(descriptionAttachmentParsed);
+ });
+
+ it('should return correct attachment text translated parsed md when translate is active', () => {
+ const textAttachment = [
+ {
+ ...attachmentMessage[0],
+ text: 'attachment not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'attachment translated',
+ },
+ },
+ ];
+ const textAttachmentParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'attachment translated',
+ },
+ ],
+ },
+ ];
+
+ expect(parseMessageAttachments(textAttachment, parseOptions, enabledAutoTranslatedOptions)[0].md).to.deep.equal(textAttachmentParsed);
+ });
+
+ it('should return correct attachment text translated parsed md when translate is active and has multiple texts', () => {
+ const quote = {
+ author_name: 'authorName',
+ author_link: 'link',
+ author_icon: 'icon',
+ message_link: 'messageLink',
+ md: [],
+ text: 'text level 2',
+ translations: { en: 'text level 2 translated' },
+ };
+ const textAttachment = [
+ {
+ ...quote,
+ text: 'attachment not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'attachment translated',
+ },
+ attachments: [quote],
+ },
+ ];
+ const textAttachmentParsed = {
+ ...textAttachment[0],
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'attachment translated',
+ },
+ ],
+ },
+ ],
+ attachments: [
+ {
+ ...quote,
+ text: 'text level 2',
+ translations: {
+ en: 'text level 2 translated',
+ },
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'text level 2 translated',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ expect(parseMessageAttachments(textAttachment, parseOptions, enabledAutoTranslatedOptions)[0]).to.deep.equal(textAttachmentParsed);
+ });
+ });
+});
diff --git a/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageQuoteAttachment.spec.ts b/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageQuoteAttachment.spec.ts
new file mode 100644
index 000000000000..69f7f35b2230
--- /dev/null
+++ b/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageQuoteAttachment.spec.ts
@@ -0,0 +1,196 @@
+/* eslint-env mocha */
+import type { Options, Root } from '@rocket.chat/message-parser';
+import type { MessageQuoteAttachment } from '@rocket.chat/core-typings';
+import { expect } from 'chai';
+
+import { parseMessageQuoteAttachment } from '../../../../../../../client/views/room/MessageList/lib/parseMessageTextToAstMarkdown';
+
+const parseOptions: Options = {
+ colors: true,
+ emoticons: true,
+ katex: {
+ dollarSyntax: true,
+ parenthesisSyntax: true,
+ },
+};
+
+const messageParserTokenMessage: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'message ',
+ },
+ {
+ type: 'BOLD',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'bold',
+ },
+ ],
+ },
+ {
+ type: 'PLAIN_TEXT',
+ value: ' ',
+ },
+ {
+ type: 'ITALIC',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'italic',
+ },
+ ],
+ },
+ {
+ type: 'PLAIN_TEXT',
+ value: ' and ',
+ },
+ {
+ type: 'STRIKE',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'strike',
+ },
+ ],
+ },
+ ],
+ },
+];
+
+const autoTranslateOptions = {
+ autoTranslateEnabled: false,
+ translated: false,
+};
+
+const quoteMessage = {
+ author_name: 'authorName',
+ author_link: 'link',
+ author_icon: 'icon',
+ text: 'message **bold** _italic_ and ~strike~',
+ md: messageParserTokenMessage,
+};
+
+describe('parseMessageQuoteAttachment', () => {
+ it('should return md property populated if the quote is parsed', () => {
+ expect(parseMessageQuoteAttachment(quoteMessage, parseOptions, autoTranslateOptions).md).to.deep.equal(messageParserTokenMessage);
+ });
+
+ it('should return md property populated if the quote is not parsed', () => {
+ expect(
+ parseMessageQuoteAttachment(
+ { ...quoteMessage, md: undefined } as unknown as MessageQuoteAttachment,
+ parseOptions,
+ autoTranslateOptions,
+ ).md,
+ ).to.deep.equal(messageParserTokenMessage);
+ });
+
+ describe('translated', () => {
+ const translatedQuote = {
+ ...quoteMessage,
+ text: 'quote not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'quote translated',
+ },
+ };
+ const translatedMessageParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'quote translated',
+ },
+ ],
+ },
+ ];
+
+ const enabledAutoTranslatedOptions = {
+ translated: true,
+ autoTranslateLanguage: 'en',
+ };
+ it('should return correct quote translated parsed md when translate is active', () => {
+ expect(parseMessageQuoteAttachment(translatedQuote, parseOptions, enabledAutoTranslatedOptions).md).to.deep.equal(
+ translatedMessageParsed,
+ );
+ });
+
+ it('should return text parsed md when translate is active and autoTranslateLanguage is undefined', () => {
+ expect(
+ parseMessageQuoteAttachment(translatedQuote, parseOptions, { ...enabledAutoTranslatedOptions, autoTranslateLanguage: undefined })
+ .md,
+ ).to.deep.equal([
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'quote not translated',
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('should return correct multiple attachment quote translated parsed md when translate is active', () => {
+ const quote = { ...quoteMessage, text: 'text level 2', translations: { en: 'text level 2 translated' } };
+
+ const multipleQuotes = {
+ ...translatedQuote,
+ attachments: [
+ {
+ ...translatedQuote,
+ text: 'text',
+ translations: {
+ en: 'text translated',
+ },
+ attachments: [quote],
+ },
+ ],
+ };
+ const multipleQuotesParsed = {
+ ...translatedQuote,
+ md: translatedMessageParsed,
+ attachments: [
+ {
+ ...multipleQuotes.attachments[0],
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'text translated',
+ },
+ ],
+ },
+ ],
+ attachments: [
+ {
+ ...quote,
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'text level 2 translated',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ expect(parseMessageQuoteAttachment(multipleQuotes, parseOptions, enabledAutoTranslatedOptions)).to.deep.equal(multipleQuotesParsed);
+ });
+ });
+});
diff --git a/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts b/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts
index 725cb672054f..efcd7725102d 100644
--- a/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts
+++ b/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts
@@ -27,8 +27,4 @@ export type MessageAttachmentDefault = {
thumb_url?: string;
color?: string;
-
- translations?: {
- [language: string]: string;
- };
} & MessageAttachmentBase;
diff --git a/packages/core-typings/src/IMessage/MessageAttachment/TranslatedMessageAttachment.ts b/packages/core-typings/src/IMessage/MessageAttachment/TranslatedMessageAttachment.ts
new file mode 100644
index 000000000000..711f56598546
--- /dev/null
+++ b/packages/core-typings/src/IMessage/MessageAttachment/TranslatedMessageAttachment.ts
@@ -0,0 +1,12 @@
+import type { MessageAttachment } from './MessageAttachment';
+import type { MessageAttachmentDefault } from './MessageAttachmentDefault';
+
+export interface ITranslatedMessageAttachment extends MessageAttachmentDefault {
+ translations: { [language: string]: string };
+}
+
+export const isTranslatedAttachment = (attachment: MessageAttachment): attachment is ITranslatedMessageAttachment =>
+ 'translations' in attachment;
+
+export const isTranslatedMessageAttachment = (attachments: MessageAttachment[]): attachments is ITranslatedMessageAttachment[] =>
+ attachments?.some(isTranslatedAttachment);
diff --git a/packages/core-typings/src/IMessage/MessageAttachment/index.ts b/packages/core-typings/src/IMessage/MessageAttachment/index.ts
index 2bbfa99f0a36..82f8613b5e29 100644
--- a/packages/core-typings/src/IMessage/MessageAttachment/index.ts
+++ b/packages/core-typings/src/IMessage/MessageAttachment/index.ts
@@ -5,3 +5,4 @@ export * from './MessageAttachmentAction';
export * from './MessageAttachmentBase';
export * from './MessageAttachmentDefault';
export * from './MessageQuoteAttachment';
+export * from './TranslatedMessageAttachment';