Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/components/ChannelPreview/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { ReactNode } from 'react';
import React from 'react';
import ReactMarkdown from 'react-markdown';
import type { ReactNode } from 'react';
import type { Channel, PollVote, TranslationLanguages, UserResponse } from 'stream-chat';
import type { Channel, PollVote, UserResponse } from 'stream-chat';

import type { TranslationContextValue } from '../../context/TranslationContext';
import type { ChatContextValue } from '../../context';
import { getTranslatedMessageText } from '../../context/MessageTranslationViewContext';
import type { TranslationContextValue } from '../../context/TranslationContext';
import type { PluggableList } from 'unified';
import { htmlToTextPlugin, imageToLink, plusPlusToEmphasis } from '../Message';
import remarkGfm from 'remark-gfm';
Expand Down Expand Up @@ -45,7 +46,7 @@ export const getLatestMessagePreview = (
channel.state.latestMessages[channel.state.latestMessages.length - 1];

const previewTextToRender =
latestMessage?.i18n?.[`${userLanguage}_text` as `${TranslationLanguages}_text`] ||
getTranslatedMessageText({ language: userLanguage, message: latestMessage }) ||
latestMessage?.text;
const poll = latestMessage?.poll;

Expand Down
11 changes: 11 additions & 0 deletions src/components/Icons/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,17 @@ export const IconThunder = createIcon(
</>,
);

export const IconTranslate = createIcon(
'IconTranslate',
<path
d='M2.5 3.87761H8.16667M5.33333 3.83333V2.5M8 9.5C5.28999 8.79887 3.89659 7.07587 3.5 4M2.66667 9.33333C5.37568 8.6532 6.7692 6.98233 7.16667 4M9.56253 11.4166H13.1041M14.1667 12.8333L11.957 6.98413C11.7399 6.40937 10.9268 6.40937 10.7097 6.98413L8.5 12.8333'
fill='none'
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
/>,
);

export const IconTrashBin = createIcon(
'IconTrashBin',
<path d='M7.99969 1.06708C9.35431 1.06708 10.4912 1.98524 10.8288 3.23309H13.8337C14.1649 3.23327 14.4333 3.50242 14.4333 3.83368C14.4331 4.16479 14.1648 4.43311 13.8337 4.43329H13.4284L12.8288 12.9684C12.7577 13.981 11.9152 14.7662 10.9001 14.7663H5.10028C4.08508 14.7663 3.24268 13.9811 3.17157 12.9684L2.57196 4.43329H2.16669C1.83542 4.43329 1.56725 4.1649 1.56708 3.83368C1.56708 3.50231 1.83532 3.23309 2.16669 3.23309H5.17157C5.50918 1.98537 6.64525 1.06722 7.99969 1.06708ZM4.36884 12.8845C4.39581 13.2686 4.71521 13.5671 5.10028 13.5671H10.9001C11.2851 13.567 11.6045 13.2685 11.6315 12.8845L12.2253 4.43329H3.77509L4.36884 12.8845ZM6.06708 10.8337V6.99969C6.06725 6.66847 6.33542 6.40009 6.66669 6.40009C6.99795 6.40009 7.26612 6.66847 7.2663 6.99969V10.8337C7.26612 11.1649 6.99795 11.4333 6.66669 11.4333C6.33542 11.4333 6.06725 11.1649 6.06708 10.8337ZM8.73309 10.8337V6.99969C8.73327 6.66847 9.00242 6.40009 9.33368 6.40009C9.66479 6.40026 9.93311 6.66858 9.93329 6.99969V10.8337C9.93311 11.1648 9.66479 11.4331 9.33368 11.4333C9.00242 11.4333 8.73327 11.1649 8.73309 10.8337ZM7.99969 2.2663C7.31836 2.26642 6.73003 2.66111 6.44696 3.23309H9.55341C9.27031 2.66094 8.68122 2.2663 7.99969 2.2663Z' />,
Expand Down
6 changes: 3 additions & 3 deletions src/components/Message/FixedHeightMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import { useMessageContext } from '../../context/MessageContext';
import { useTranslationContext } from '../../context/TranslationContext';
import { renderText } from './renderText';

import type { LocalMessage, TranslationLanguages } from 'stream-chat';
import type { LocalMessage } from 'stream-chat';
import { ModalGallery } from '../Attachment';
import { getTranslatedMessageText } from '../../context/MessageTranslationViewContext';

const selectColor = (number: number, dark: boolean) => {
const hue = number * 137.508; // use golden angle approximation
Expand Down Expand Up @@ -58,8 +59,7 @@ const UnMemoizedFixedHeightMessage = (props: FixedHeightMessageProps) => {
const role = useUserRole(message);

const messageTextToRender =
message?.i18n?.[`${userLanguage}_text` as `${TranslationLanguages}_text`] ||
message?.text;
getTranslatedMessageText({ language: userLanguage, message }) || message?.text;

const renderedText = useMemo(
() => renderText(messageTextToRender, message.mentioned_users),
Expand Down
11 changes: 11 additions & 0 deletions src/components/Message/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
useChannelStateContext,
useChatContext,
useComponentContext,
useMessageTranslationViewContext,
} from '../../context';

import { MessageSimple as DefaultMessage } from './MessageSimple';
Expand Down Expand Up @@ -74,6 +75,14 @@ const MessageWithContext = (props: MessageWithContextProps) => {
const { client, isMessageAIGenerated } = useChatContext('Message');
const { channelConfig, read } = useChannelStateContext('Message');
const { Message: contextMessage } = useComponentContext('Message');
const { getTranslationView, setTranslationView: setTranslationViewInContext } =
useMessageTranslationViewContext();

const translationView = getTranslationView(message.id, !!message.i18n);
const setTranslationView = useCallback(
(view: 'original' | 'translated') => setTranslationViewInContext(message.id, view),
[message.id, setTranslationViewInContext],
);

const actionsEnabled = message.type === 'regular' && message.status === 'received';
const MessageUIComponent = propMessage ?? contextMessage ?? DefaultMessage;
Expand Down Expand Up @@ -161,6 +170,8 @@ const MessageWithContext = (props: MessageWithContextProps) => {
messageIsUnread,
onUserClick,
onUserHover,
setTranslationView,
translationView,
};

return (
Expand Down
3 changes: 3 additions & 0 deletions src/components/Message/MessageSimple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { isDateSeparatorMessage } from '../MessageList';
import { MessageAlsoSentInChannelIndicator as DefaultMessageAlsoSentInChannelIndicator } from './MessageAlsoSentInChannelIndicator';
import { MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageIsThreadReplyInChannelButtonIndicator';
import { ReminderNotification as DefaultReminderNotification } from './ReminderNotification';
import { MessageTranslationIndicator as DefaultMessageTranslationIndicator } from './MessageTranslationIndicator';
import { useMessageReminder } from './hooks';
import {
areMessageUIPropsEqual,
Expand Down Expand Up @@ -89,6 +90,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => {
MessageRepliesCountButton = DefaultMessageRepliesCountButton,
MessageStatus = DefaultMessageStatus,
MessageTimestamp = DefaultMessageTimestamp,
MessageTranslationIndicator = DefaultMessageTranslationIndicator,
PinIndicator = DefaultPinIndicator,
QuotedMessage = DefaultQuotedMessage,
ReactionsList = DefaultReactionList,
Expand Down Expand Up @@ -193,6 +195,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => {
{message.pinned && <PinIndicator message={message} />}
{threadList && message.show_in_channel && <MessageAlsoSentInChannelIndicator />}
{!!reminder && <ReminderNotification reminder={reminder} />}
<MessageTranslationIndicator message={message} />
{message.user && (
<Avatar
className='str-chat__avatar--with-border'
Expand Down
9 changes: 6 additions & 3 deletions src/components/Message/MessageText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { useMessageContext, useTranslationContext } from '../../context';
import { renderText as defaultRenderText } from './renderText';
import { MessageErrorText } from './MessageErrorText';

import type { LocalMessage, TranslationLanguages } from 'stream-chat';
import type { LocalMessage } from 'stream-chat';
import { getTranslatedMessageText } from '../../context/MessageTranslationViewContext';

export type MessageTextProps = {
/* Replaces the CSS class name placed on the component's inner `div` container */
Expand All @@ -31,6 +32,7 @@ const UnMemoizedMessageTextComponent = (props: MessageTextProps) => {
onMentionsClickMessage,
onMentionsHoverMessage,
renderText: contextRenderText,
translationView = 'translated',
unsafeHTML,
} = useMessageContext('MessageText');

Expand All @@ -41,8 +43,9 @@ const UnMemoizedMessageTextComponent = (props: MessageTextProps) => {
const hasAttachment = messageHasAttachments(message);

const messageTextToRender =
message.i18n?.[`${userLanguage}_text` as `${TranslationLanguages}_text`] ||
message.text;
translationView === 'original'
? message.text
: getTranslatedMessageText({ language: userLanguage, message }) || message.text;

const messageText = useMemo(
() => renderText(messageTextToRender, message.mentioned_users),
Expand Down
83 changes: 83 additions & 0 deletions src/components/Message/MessageTranslationIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { LocalMessage } from 'stream-chat';
import React, { useCallback, useMemo } from 'react';
import { IconTranslate } from '../Icons';
import {
getTranslatedMessageText,
useMessageContext,
useTranslationContext,
} from '../../context';
import { Button } from '../Button';

export type TranslationIndicatorProps = {
message?: LocalMessage;
};

export const MessageTranslationIndicator = ({
message: propMessage,
}: TranslationIndicatorProps) => {
const { t, userLanguage } = useTranslationContext();
const {
message: contextMessage,
setTranslationView,
translationView,
} = useMessageContext('MessageTranslationIndicator');
const message = propMessage ?? contextMessage;

const translatedTextForUser = useMemo(
() => getTranslatedMessageText({ language: userLanguage, message }),
[userLanguage, message],
);

const hasTranslationForUserLanguage = useMemo(
() =>
translatedTextForUser != null &&
message?.text !== undefined &&
translatedTextForUser !== message.text,
[translatedTextForUser, message?.text],
);

const viewingOriginal = useMemo(
() =>
translationView === 'original' ||
(translationView === undefined && !hasTranslationForUserLanguage),
[translationView, hasTranslationForUserLanguage],
);

const handleToggle = useCallback(() => {
setTranslationView?.(viewingOriginal ? 'translated' : 'original');
}, [setTranslationView, viewingOriginal]);

const sourceLanguageName = useMemo(() => {
const sourceLanguageCode = message?.i18n?.language;
if (!sourceLanguageCode) return '';
const languageKey = 'language/' + sourceLanguageCode;
const translatedName = t(languageKey);
return translatedName && translatedName !== languageKey
? translatedName
: sourceLanguageCode;
}, [message?.i18n?.language, t]);

if (!message?.i18n || !setTranslationView) return null;
if (!hasTranslationForUserLanguage) return null;

return (
<div className='str-chat__message-translation-indicator'>
<IconTranslate />
<span className='str-chat__message-translation-indicator__sign'>
{viewingOriginal
? t('Original')
: sourceLanguageName
? t('Translated from {{ language }}', { language: sourceLanguageName })
: t('Translated')}
</span>
<span> · </span>
<Button
className='str-chat__message-translation-indicator__translation-toggle'
onClick={handleToggle}
type='button'
>
{viewingOriginal ? t('View translation') : t('View original')}
</Button>
</div>
);
};
1 change: 1 addition & 0 deletions src/components/Message/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './MessageSimple';
export * from './MessageStatus';
export * from './MessageText';
export * from './MessageTimestamp';
export * from './MessageTranslationIndicator';
export * from './QuotedMessage';
export * from './ReminderNotification';
export * from './renderText';
Expand Down
3 changes: 3 additions & 0 deletions src/components/Message/styling/Message.scss
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
'pin-indicator'
'also-sent-in-channel'
'message-reminder'
'translation-indicator'
'message'
'translation-notice'
'custom-metadata'
Expand All @@ -243,6 +244,7 @@
'. pin-indicator'
'. also-sent-in-channel'
'. message-reminder'
'. translation-indicator'
'avatar message'
'avatar translation-notice'
'avatar custom-metadata'
Expand All @@ -256,6 +258,7 @@
'pin-indicator .'
'also-sent-in-channel .'
'message-reminder .'
'translation-indicator .'
'message avatar'
'translation-notice avatar'
'custom-metadata avatar'
Expand Down
33 changes: 33 additions & 0 deletions src/components/Message/styling/MessageTranslationIndicator.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@use '../../../styling/utils';

.str-chat {
--str-chat__message-translation-indicator-color: var(
--text-primary-text,
var(--text-primary)
);
--str-chat__message-translation-indicator-background-color: transparent;
}

/* Translation indicator: below reminder in message grid. */
.str-chat__message-translation-indicator {
display: flex;
align-items: center;
grid-area: translation-indicator;
padding-block: var(--spacing-xxs);
gap: var(--spacing-xxs);
margin: 0;
color: var(--str-chat__message-translation-indicator-color);
@include utils.component-layer-overrides('message-translation-indicator');

&, .str-chat__message-translation-indicator__translation-toggle {
font: var(--str-chat__metadata-default-text);
}

.str-chat__message-translation-indicator__sign {
font: var(--str-chat__metadata-emphasis-text);
}

svg path {
stroke-width: 1.5px;
}
}
1 change: 1 addition & 0 deletions src/components/Message/styling/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@use 'MessageIsThreadReplyInChannelButtonIndicator';
@use 'MessageStatus';
@use 'MessageSystem';
@use 'MessageTranslationIndicator';
@use 'QuotedMessage';
@use 'ReminderNotification';
@use 'UnreadMessageNotification';
Expand Down
Loading
Loading