Skip to content

Commit 585ad5f

Browse files
authored
Merge pull request #683 from GetStream/auto-translation-text
Auto translation text
2 parents bd8b037 + d458f0e commit 585ad5f

File tree

11 files changed

+191
-66
lines changed

11 files changed

+191
-66
lines changed

.eslintrc.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
"import/extensions": [0],
3232
"max-classes-per-file": 0,
3333
"camelcase": 0,
34-
"react-hooks/rules-of-hooks": 2,
35-
"react-hooks/exhaustive-deps": 1,
34+
"react-hooks/rules-of-hooks": 1,
35+
"react-hooks/exhaustive-deps": 0,
3636
"jest/prefer-inline-snapshots": 0,
3737
"jest/lowercase-name": 0,
3838
"jest/prefer-expect-assertions": 0,

src/components/Chat/Chat.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ const Chat = ({
3737
/** @type { Required<import('types').TranslationContextValue>} */ ({
3838
t: /** @param {string} key */ (key) => key,
3939
tDateTimeParser: (input) => Dayjs(input),
40+
userLanguage: '',
4041
}),
4142
);
43+
4244
const [mutes, setMutes] = useState(
4345
/** @type {import('stream-chat').Mute[]} */ ([]),
4446
);
@@ -81,9 +83,14 @@ const Chat = ({
8183
setTranslators((prevTranslator) => ({ ...prevTranslator, t })),
8284
);
8385
streami18n.getTranslators().then((translator) => {
84-
if (translator) setTranslators(translator);
86+
if (translator) {
87+
setTranslators({
88+
...translator,
89+
userLanguage: client?.user?.language || '',
90+
});
91+
}
8592
});
86-
}, [i18nInstance]);
93+
}, [client, i18nInstance]);
8794

8895
const setActiveChannel = useCallback(
8996
/**

src/components/Message/FixedHeightMessage.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import MessageTimestamp from './MessageTimestamp';
55
import { Avatar } from '../Avatar';
66
import { MML } from '../MML';
77
import { renderText } from '../../utils';
8-
import { ChatContext } from '../../context';
8+
import { ChatContext, TranslationContext } from '../../context';
99
import { Gallery } from '../Gallery';
1010
import { MessageActions } from '../MessageActions';
1111
import { useUserRole, useActionHandler } from './hooks';
@@ -46,12 +46,18 @@ const getUserColor = (theme, userId) => {
4646
*/
4747
const FixedHeightMessage = ({ message, groupedByUser }) => {
4848
const { theme } = useContext(ChatContext);
49+
const { userLanguage } = useContext(TranslationContext);
50+
4951
const role = useUserRole(message);
5052
const handleAction = useActionHandler(message);
5153

54+
const messageTextToRender =
55+
// @ts-expect-error
56+
message?.i18n?.[`${userLanguage}_text`] || message?.text;
57+
5258
const renderedText = useMemo(
53-
() => renderText(message.text, message.mentioned_users),
54-
[message.text, message.mentioned_users],
59+
() => renderText(messageTextToRender, message.mentioned_users),
60+
[message.mentioned_users, messageTextToRender],
5561
);
5662
const userId = message.user?.id;
5763
// @ts-ignore

src/components/Message/MessageLivestream.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ const MessageLivestreamComponent = (props) => {
8383
MessageDeleted,
8484
PinIndicator = DefaultPinIndicator,
8585
} = props;
86-
const { t: contextT } = useContext(TranslationContext);
86+
const { t: contextT, userLanguage } = useContext(TranslationContext);
8787
const t = propT || contextT;
8888
const messageWrapperRef = useRef(null);
8989
const reactionSelectorRef = useRef(null);
@@ -120,11 +120,12 @@ const MessageLivestreamComponent = (props) => {
120120
onUserClickHandler: propOnUserClick,
121121
onUserHoverHandler: propOnUserHover,
122122
});
123-
const messageTextItem = message?.text;
123+
const messageTextToRender =
124+
message?.i18n?.[`${userLanguage}_text`] || message?.text;
124125
const messageMentionedUsersItem = message?.mentioned_users;
125126
const messageText = useMemo(
126-
() => renderText(messageTextItem, messageMentionedUsersItem),
127-
[messageTextItem, messageMentionedUsersItem],
127+
() => renderText(messageTextToRender, messageMentionedUsersItem),
128+
[messageMentionedUsersItem, messageTextToRender],
128129
);
129130

130131
const firstGroupStyle = groupStyles ? groupStyles[0] : '';

src/components/Message/MessageTeam.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const MessageTeam = (props) => {
8787
ChannelContext,
8888
);
8989
const channelConfig = propChannelConfig || channel?.getConfig();
90-
const { t: contextT } = useContext(TranslationContext);
90+
const { t: contextT, userLanguage } = useContext(TranslationContext);
9191
const t = propT || contextT;
9292
const groupStyles = props.groupStyles || ['single'];
9393
const reactionSelectorRef = useRef(null);
@@ -119,11 +119,13 @@ const MessageTeam = (props) => {
119119
onUserClickHandler: propOnUserClick,
120120
onUserHoverHandler: propOnUserHover,
121121
});
122-
const messageTextItem = message?.text;
122+
const messageTextToRender =
123+
message?.i18n?.[`${userLanguage}_text`] || message?.text;
123124
const messageMentionedUsersItem = message?.mentioned_users;
125+
// eslint-disable-next-line react-hooks/rules-of-hooks
124126
const messageText = useMemo(
125-
() => renderText(messageTextItem, messageMentionedUsersItem),
126-
[messageTextItem, messageMentionedUsersItem],
127+
() => renderText(messageTextToRender, messageMentionedUsersItem),
128+
[messageMentionedUsersItem, messageTextToRender],
127129
);
128130
const firstGroupStyle = groupStyles ? groupStyles[0] : '';
129131

src/components/Message/MessageText.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,36 @@ const MessageTextComponent = (props) => {
3030
unsafeHTML,
3131
customOptionProps,
3232
} = props;
33+
3334
const reactionSelectorRef = useRef(
3435
/** @type {HTMLDivElement | null} */ (null),
3536
);
37+
3638
const { onMentionsClick, onMentionsHover } = useMentionsUIHandler(message, {
3739
onMentionsClick: propOnMentionsClick,
3840
onMentionsHover: propOnMentionsHover,
3941
});
42+
4043
const {
4144
onReactionListClick,
4245
showDetailedReactions,
4346
isReactionEnabled,
4447
} = useReactionClick(message, reactionSelectorRef);
45-
const { t } = useContext(TranslationContext);
48+
49+
const { t, userLanguage } = useContext(TranslationContext);
50+
4651
const hasReactions = messageHasReactions(message);
4752
const hasAttachment = messageHasAttachments(message);
4853
const handleReaction = useReactionHandler(message);
49-
const messageTextItem = message?.text;
54+
55+
const messageTextToRender =
56+
message?.i18n?.[`${userLanguage}_text`] || message?.text;
57+
5058
const messageMentionedUsersItem = message?.mentioned_users;
59+
5160
const messageText = useMemo(
52-
() => renderText(messageTextItem, messageMentionedUsersItem),
53-
[messageTextItem, messageMentionedUsersItem],
61+
() => renderText(messageTextToRender, messageMentionedUsersItem),
62+
[messageMentionedUsersItem, messageTextToRender],
5463
);
5564

5665
const wrapperClass = customWrapperClass || 'str-chat__message-text';

src/components/Message/__tests__/FixedHeightMessage.test.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import {
99
} from 'mock-builders';
1010

1111
import FixedHeightMessage from '../FixedHeightMessage';
12-
import { ChatContext, ChannelContext } from '../../../context';
12+
import {
13+
ChatContext,
14+
ChannelContext,
15+
TranslationContext,
16+
} from '../../../context';
1317
import { Avatar as AvatarMock } from '../../Avatar';
1418
import { MML as MMLMock } from '../../MML';
1519
import { Gallery as GalleryMock } from '../../Gallery';
@@ -29,11 +33,20 @@ const bob = generateUser({ name: 'bob' });
2933
async function renderMsg(message) {
3034
const channel = generateChannel();
3135
const client = await getTestClientWithUser(alice);
36+
const customDateTimeParser = jest.fn(() => ({ format: jest.fn() }));
3237

3338
return render(
3439
<ChatContext.Provider value={{ theme: 'dark' }}>
3540
<ChannelContext.Provider value={{ client, channel }}>
36-
<FixedHeightMessage message={message} />
41+
<TranslationContext.Provider
42+
value={{
43+
t: (key) => key,
44+
tDateTimeParser: customDateTimeParser,
45+
userLanguage: 'en',
46+
}}
47+
>
48+
<FixedHeightMessage message={message} />
49+
</TranslationContext.Provider>
3750
</ChannelContext.Provider>
3851
</ChatContext.Provider>,
3952
);
@@ -91,4 +104,16 @@ describe('<FixedHeightMessage />', () => {
91104
await renderMsg(message);
92105
expect(MessageActionsMock).toHaveReturnedWith([]);
93106
});
107+
108+
it('should display text in users set language', async () => {
109+
const message = generateMessage({
110+
user: alice,
111+
i18n: { fr_text: 'bonjour', en_text: 'hello', language: 'fr' },
112+
text: 'bonjour',
113+
});
114+
115+
const { getByText } = await renderMsg(message);
116+
117+
expect(getByText('hello')).toBeInTheDocument();
118+
});
94119
});

src/components/Message/__tests__/MessageLivestream.test.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import MessageLivestream from '../MessageLivestream';
1313
import { Avatar as AvatarMock } from '../../Avatar';
1414
import { MessageInput as MessageInputMock } from '../../MessageInput';
1515
import { MessageActions as MessageActionsMock } from '../../MessageActions';
16-
import { ChannelContext } from '../../../context';
16+
import { ChannelContext, TranslationContext } from '../../../context';
1717

1818
jest.mock('../../Avatar', () => ({
1919
Avatar: jest.fn(() => <div />),
@@ -37,9 +37,19 @@ async function renderMessageLivestream(
3737
) {
3838
const channel = generateChannel({ getConfig: () => channelConfig });
3939
const client = await getTestClientWithUser(alice);
40+
const customDateTimeParser = jest.fn(() => ({ format: jest.fn() }));
41+
4042
return render(
4143
<ChannelContext.Provider value={{ client, channel }}>
42-
<MessageLivestream message={message} typing={false} {...props} />
44+
<TranslationContext.Provider
45+
value={{
46+
t: (key) => key,
47+
tDateTimeParser: customDateTimeParser,
48+
userLanguage: 'en',
49+
}}
50+
>
51+
<MessageLivestream message={message} typing={false} {...props} />
52+
</TranslationContext.Provider>
4353
</ChannelContext.Provider>,
4454
);
4555
}
@@ -385,6 +395,18 @@ describe('<MessageLivestream />', () => {
385395
expect(getByTestId(messageLivestreamthreadTestId)).toBeInTheDocument();
386396
});
387397

398+
it('should display text in users set language', async () => {
399+
const message = generateAliceMessage({
400+
i18n: { fr_text: 'bonjour', en_text: 'hello', language: 'fr' },
401+
text: 'bonjour',
402+
});
403+
404+
const { getByText, debug } = await renderMessageLivestream(message);
405+
debug();
406+
407+
expect(getByText('hello')).toBeInTheDocument();
408+
});
409+
388410
it('should open thread when thread action button is clicked', async () => {
389411
const message = generateAliceMessage();
390412
const handleOpenThread = jest.fn();

src/components/Message/__tests__/MessageTeam.test.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
generateReaction,
1111
} from 'mock-builders';
1212

13-
import { ChannelContext } from '../../../context';
13+
import { ChannelContext, TranslationContext } from '../../../context';
1414
import MessageTeam from '../MessageTeam';
1515
import { Avatar as AvatarMock } from '../../Avatar';
1616
import { MML as MMLMock } from '../../MML';
@@ -42,9 +42,19 @@ async function renderMessageTeam(
4242
) {
4343
const channel = generateChannel({ getConfig: () => channelConfig });
4444
const client = await getTestClientWithUser(alice);
45+
const customDateTimeParser = jest.fn(() => ({ format: jest.fn() }));
46+
4547
return render(
4648
<ChannelContext.Provider value={{ client, channel, t: (key) => key }}>
47-
<MessageTeam message={message} typing={false} {...props} />
49+
<TranslationContext.Provider
50+
value={{
51+
t: (key) => key,
52+
tDateTimeParser: customDateTimeParser,
53+
userLanguage: 'en',
54+
}}
55+
>
56+
<MessageTeam message={message} typing={false} {...props} />
57+
</TranslationContext.Provider>
4858
</ChannelContext.Provider>,
4959
);
5060
}
@@ -294,6 +304,17 @@ describe('<MessageTeam />', () => {
294304
);
295305
});
296306

307+
it('should display text in users set language', async () => {
308+
const message = generateAliceMessage({
309+
i18n: { fr_text: 'bonjour', en_text: 'hello', language: 'fr' },
310+
text: 'bonjour',
311+
});
312+
313+
const { getByText } = await renderMessageTeam(message);
314+
315+
expect(getByText('hello')).toBeInTheDocument();
316+
});
317+
297318
it('should place a spacer when message is not the first message on a thread and group style is not top or single', async () => {
298319
const message = generateAliceMessage();
299320
const { getByTestId } = await renderMessageTeam(message, {

0 commit comments

Comments
 (0)