Fixed issue when user display names containing emoji are translated

Created a <Twemojify> component which can be passed through translation as a component, which then twemojify's the user name after translation
This commit is contained in:
Dylan 2022-08-07 12:06:28 +09:30
parent 4e0707953c
commit 7627d43e92
6 changed files with 158 additions and 95 deletions

View file

@ -262,10 +262,10 @@
"jump_unread": "Jump to unread messages",
"mark_read": "Mark as read",
"jump_latest": "Jump to latest",
"user_typing_one": "<bold>{{user_one}}</bold> is typing...",
"user_typing_two": "<bold>{{user_one}}</bold> and <bold>{{user_two}}</bold> are typing...",
"user_typing_three": "<bold>{{user_one}}</bold>, <bold>{{user_two}}</bold> and <bold>{{user_three}}</bold> are typing...",
"user_typing_four": "<bold>{{user_one}}</bold>, <bold>{{user_two}}</bold>, <bold>{{user_three}}</bold> and <bold>{{user_four}}</bold> are typing...",
"user_typing_1": "<bold><user_one/></bold> is typing...",
"user_typing_2": "<bold><user_one/></bold> and <bold><user_two/></bold> are typing...",
"user_typing_3": "<bold><user_one/></bold>, <bold><user_two/></bold> and <bold><user_three/></bold> are typing...",
"user_typing_4": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and <bold><user_four/></bold> are typing...",
"user_typing_other": "<bold>Several people</bold> are typing..."
},
"RoomViewContent": {
@ -287,20 +287,23 @@
"room_settings_subtitle": "room settings"
},
"RoomCommon": {
"user_joined": "<bold>{{user_name}}</bold> joined the room",
"user_left": "<bold>{{user_name}}</bold> left the room",
"user_invited": "<bold>{{inviter_name}}</bold> invited <bold>{{user_name}}</bold>",
"invite_cancelled": "<bold>{{inviter_name}}</bold> cancelled <bold>{{user_name}}'s</bold> invite",
"invite_rejected": "<bold>{{user_name}}</bold> rejected the invitation",
"user_kicked": "<bold>{{actor}}</bold> kicked <bold>{{user_name}}</bold>: {{reason}}",
"user_banned": "<bold>{{actor}}</bold> banned <bold>{{user_name}}</bold>: {{reason}}",
"user_unbanned": "<bold>{{actor}} unbanned <bold>{{user_name}}</bold>",
"avatar_set": "<bold>{{user_name}}</bold> set an avatar",
"avatar_changed": "<bold>{{user_name}}</bold> changed their avatar",
"avatar_removed": "<bold>{{user_name}}</bold> removed their avatar",
"name_set": "<bold>{{user_name}}</bold> set their display name to <bold>{{new_name}}</bold>",
"name_changed": "<bold>{{user_name}}</bold> changed their display name to <bold>{{new_name}}</bold>",
"name_removed": "<bold>{{user_name}}</bold> removed their display name <bold>{{new_name}}</bold>"
"user_joined": "<bold><user/></bold> joined the room",
"user_left": "<bold><user/></bold> left the room",
"user_left_reason": "<bold><user/></bold> left the room: <reason/>",
"user_invited": "<bold><inviter/></bold> invited <bold><user/></bold>",
"invite_cancelled": "<bold><inviter/></bold> cancelled <bold><user/>'s</bold> invite",
"invite_rejected": "<bold><user/></bold> rejected the invitation",
"user_kicked": "<bold><actor/></bold> kicked <bold><user/></bold>",
"user_kicked_reason": "<bold><actor/></bold> kicked <bold><user/></bold>: <reason/>",
"user_banned": "<bold><actor/></bold> banned <bold><user/></bold>",
"user_banned_reason": "<bold><actor/></bold> banned <bold><user/></bold>: <reason/>",
"user_unbanned": "<bold><actor/> unbanned <bold><user/></bold>",
"avatar_set": "<bold><user/></bold> set an avatar",
"avatar_changed": "<bold><user/></bold> changed their avatar",
"avatar_removed": "<bold><user/></bold> removed their avatar",
"name_set": "<bold><user/></bold> set their display name to <bold><new_name/></bold>",
"name_changed": "<bold><user/></bold> changed their display name to <bold><new_name/></bold>",
"name_removed": "<bold><user/></bold> removed their display name <bold><last_name/></bold>"
},
"PublicRooms": {
"could_not_join_alias": "Unable to join {{alias}}. Either the room is private or doesn't exist",
@ -461,10 +464,10 @@
"close_tooltip": "Close"
},
"FollowingMembers": {
"users_following_one": "<bold>{{user_one}}</bold> is following the conversation",
"users_following_two": "<bold>{{user_one}}</bold> and <bold>{{user_two}}</bold> are following the conversation",
"users_following_three": "<bold>{{user_one}}</bold>, <bold>{{user_two}}</bold>, and <bold>{{user_three}}</bold> are following the conversation",
"users_following_other": "<bold>{{user_one}}</bold>, <bold>{{user_two}}</bold>, <bold>{{user_three}}</bold> and {{other_count}} others are following the conversation"
"users_following_1": "<bold><user_one/></bold> is following the conversation",
"users_following_2": "<bold><user_one/></bold> and <bold><user_two/></bold> are following the conversation",
"users_following_3": "<bold><user_one/></bold>, <bold><user_two/></bold>, and <bold><user_three/></bold> are following the conversation",
"users_following_other": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and {{other_count}} others are following the conversation"
},
"ImageUpload": {
"prompt": "Upload",
@ -499,10 +502,10 @@
"unknown_user": "** Unknown user **",
"edited": "(edited)",
"edit_placeholder": "Edit message",
"user_reacted_one": "<bold>{{user_one}}</bold> reacted with <emoji/>",
"user_reacted_two": "<bold>{{user_one}}</bold> and <bold>{{user_two}}</bold> reacted with <emoji/>",
"user_reacted_three": "<bold>{{user_one}}</bold>, <bold>{{user_two}}</bold> and <bold>{{user_three}}</bold> reacted with <emoji/>",
"user_reacted_other": "<bold>{{user_one}}</bold>, <bold>{{user_two}}</bold>, <bold>{{user_three}}</bold> and {{other_count}} others reacted with <emoji/>",
"user_reacted_1": "<bold><user_one/></bold> reacted with <emoji/>",
"user_reacted_2": "<bold><user_one/></bold> and <bold><user_two/></bold> reacted with <emoji/>",
"user_reacted_3": "<bold><user_one/></bold>, <bold><user_two/></bold> and <bold><user_three/></bold> reacted with <emoji/>",
"user_reacted_other": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and {{other_count}} others reacted with <emoji/>",
"add_reaction_tooltip": "Add reaction",
"reply_tooltip": "Reply",
"edit_tooltip": "Edit",

View file

@ -12,7 +12,7 @@ import Text from '../../atoms/text/Text';
import RawIcon from '../../atoms/system-icons/RawIcon';
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
import { twemojify } from '../../../util/twemojify';
import { twemojify, Twemojify } from '../../../util/twemojify';
import '../../i18n';
@ -43,6 +43,12 @@ function FollowingMembers({ roomTimeline }) {
const filteredM = followingMembers.filter((userId) => userId !== myUserId);
let i18nKey = 'Molecules.FollowingMembers.users_following';
if (filteredM.length <= 3) {
i18nKey += `_${filteredM.length}`;
}
return filteredM.length !== 0 && (
<button
className="following-members"
@ -55,15 +61,17 @@ function FollowingMembers({ roomTimeline }) {
/>
<Text variant="b2">
<Trans
i18nKey="Molecules.FollowingMembers.users_following"
i18nKey={i18nKey}
values={{
count: filteredM.length,
user_one: twemojify(getUserDisplayName(room, filteredM?.[0])),
user_two: twemojify(getUserDisplayName(room, filteredM?.[1])),
user_three: twemojify(getUserDisplayName(room, filteredM?.[2])),
other_count: filteredM.length - 3,
}}
components={{ bold: <b /> }}
components={{
bold: <b />,
user_one: <Twemojify text={getUserDisplayName(room, filteredM?.[0])} />,
user_two: <Twemojify text={getUserDisplayName(room, filteredM?.[1])} />,
user_three: <Twemojify text={getUserDisplayName(room, filteredM?.[2])} />,
}}
/>
</Text>
</button>

View file

@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import './Message.scss';
import { useTranslation, Trans } from 'react-i18next';
import { twemojify } from '../../../util/twemojify';
import { twemojify, Twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import { getUsername, getUsernameOfRoomMember, parseReply } from '../../../util/matrixUtil';
@ -353,21 +353,24 @@ function pickEmoji(e, roomId, eventId, roomTimeline) {
}
function genReactionMsg(userIds, reaction, shortcode) {
let i18nKey = 'Molecules.Message.user_reacted';
if (userIds.length <= 3) {
i18nKey += `_${userIds.length}`;
}
return (
<>
{userIds.map((userId, index) => (
<React.Fragment key={userId}>
{twemojify(getUsername(userId))}
{index < userIds.length - 1 && (
<span style={{ opacity: '.6' }}>
{index === userIds.length - 2 ? ' and ' : ', '}
</span>
)}
</React.Fragment>
))}
<span style={{ opacity: '.6' }}>{' reacted with '}</span>
{twemojify(shortcode ? `:${shortcode}:` : reaction, { className: 'react-emoji' })}
</>
<Trans
i18nKey={i18nKey}
values={{ count: userIds.length, other_count: userIds.length - 3 }}
components={{
bold: <b />,
user_one: <Twemojify text={getUsername(userIds?.[0])} />,
user_two: <Twemojify text={getUsername(userIds?.[1])} />,
user_three: <Twemojify text={getUsername(userIds?.[2])} />,
emoji: <Twemojify text={shortcode ? `:${shortcode}:` : reaction} />,
}}
/>
);
}

View file

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import './RoomViewFloating.scss';
import { useTranslation, Trans } from 'react-i18next';
import { twemojify } from '../../../util/twemojify';
import { twemojify, Twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
@ -109,11 +109,13 @@ function RoomViewFloating({
setIsAtBottom(true);
};
console.log(typingMembers);
const typingMemberValues = [...typingMembers];
console.log(typingMemberValues);
let i18nKey = 'Organisms.RoomViewFloating.user_typing';
if (typingMemberValues.length <= 4) {
i18nKey += `_${typingMemberValues.length}`;
}
return (
<>
@ -129,15 +131,17 @@ function RoomViewFloating({
<div className="bouncing-loader"><div /></div>
<Text variant="b2">
<Trans
i18nKey="Organisms.RoomViewFloating.user_typing"
i18nKey={i18nKey}
values={{
count: typingMembers.size,
user_one: twemojify(getUserDisplayName(typingMemberValues?.[0])),
user_two: twemojify(getUserDisplayName(typingMemberValues?.[1])),
user_three: twemojify(getUserDisplayName(typingMemberValues?.[2])),
user_four: twemojify(getUserDisplayName(typingMemberValues?.[3])),
}}
components={{ bold: <b /> }}
components={{
bold: <b />,
user_one: <Twemojify text={getUsername(typingMemberValues?.[0])} />,
user_two: <Twemojify text={getUsername(typingMemberValues?.[1])} />,
user_three: <Twemojify text={getUsername(typingMemberValues?.[2])} />,
user_four: <Twemojify text={getUsername(typingMemberValues?.[3])} />,
}}
/>
</Text>
</div>

View file

@ -1,7 +1,7 @@
import React from 'react';
import { Trans } from 'react-i18next';
import { twemojify } from '../../../util/twemojify';
import { twemojify, Twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import { getUsername, getUsernameOfRoomMember } from '../../../util/matrixUtil';
@ -15,18 +15,20 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.user_joined"
values={{ user_name: twemojify(user) }}
components={{ bold: <b /> }}
components={{ bold: <b />, user: <Twemojify text={user} /> }}
/>
);
},
leave(user, reason) {
const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : '';
return (
<Trans
i18nKey="Organisms.RoomCommon.user_left"
values={{ user_name: twemojify(user) }}
components={{ bold: <b /> }}
context={(typeof reason === 'string') ? 'reason' : null}
components={{
bold: <b />,
user: <Twemojify text={user} />,
reason: <Twemojify text={reason} />,
}}
/>
);
},
@ -34,8 +36,11 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.user_invited"
values={{ user_name: twemojify(user), inviter_name: twemojify(inviter) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
inviter: <Twemojify text={inviter} />,
}}
/>
);
},
@ -43,8 +48,11 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.invite_cancelled"
values={{ user_name: twemojify(user), inviter_name: twemojify(inviter) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
inviter: <Twemojify text={inviter} />,
}}
/>
);
},
@ -52,36 +60,38 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.invite_rejected"
values={{ user_name: twemojify(user) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
}}
/>
);
},
kick(actor, user, reason) {
const reasonMsg = (typeof reason === 'string') ? `${reason}` : '';
return (
<Trans
i18nKey="Organisms.RoomCommon.user_kicked"
values={{
user_name: twemojify(user),
actor: twemojify(actor),
reason: twemojify(reasonMsg),
context={(typeof reason === 'string') ? 'reason' : null}
components={{
bold: <b />,
user: <Twemojify text={user} />,
actor: <Twemojify text={actor} />,
reason: <Twemojify text={reason} />,
}}
components={{ bold: <b /> }}
/>
);
},
ban(actor, user, reason) {
const reasonMsg = (typeof reason === 'string') ? `${reason}` : '';
return (
<Trans
i18nKey="Organisms.RoomCommon.user_banned"
values={{
user_name: twemojify(user),
actor: twemojify(actor),
reason: twemojify(reasonMsg),
context={(typeof reason === 'string') ? 'reason' : null}
components={{
bold: <b />,
user: <Twemojify text={user} />,
actor: <Twemojify text={actor} />,
reason: <Twemojify text={reason} />,
}}
components={{ bold: <b /> }}
/>
);
},
@ -89,8 +99,11 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.user_unbanned"
values={{ user_name: twemojify(user), actor: twemojify(actor) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
actor: <Twemojify text={actor} />,
}}
/>
);
},
@ -98,8 +111,10 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.avatar_set"
values={{ user_name: twemojify(user) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
}}
/>
);
},
@ -107,8 +122,10 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.avatar_changed"
values={{ user_name: twemojify(user) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
}}
/>
);
},
@ -116,8 +133,10 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.avatar_removed"
values={{ user_name: twemojify(user) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
}}
/>
);
},
@ -125,8 +144,11 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.name_set"
values={{ user_name: twemojify(user), new_name: twemojify(newName) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
new_name: <Twemojify text={newName} />,
}}
/>
);
},
@ -134,8 +156,11 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.name_changed"
values={{ user_name: twemojify(user), new_name: twemojify(newName) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
new_name: <Twemojify text={newName} />,
}}
/>
);
},
@ -143,8 +168,11 @@ function getTimelineJSXMessages() {
return (
<Trans
i18nKey="Organisms.RoomCommon.name_removed"
values={{ user_name: twemojify(user), new_name: twemojify(lastName) }}
components={{ bold: <b /> }}
components={{
bold: <b />,
user: <Twemojify text={user} />,
last_name: <Twemojify text={lastName} />,
}}
/>
);
},

View file

@ -5,6 +5,7 @@ import linkifyHtml from 'linkify-html';
import parse from 'html-react-parser';
import twemoji from 'twemoji';
import { sanitizeText } from './sanitize';
import PropTypes from 'prop-types';
const Math = lazy(() => import('../app/atoms/math/Math'));
@ -51,3 +52,19 @@ export function twemojify(text, opts, linkify = false, sanitize = true, maths =
}
return parse(content, maths ? mathOptions : null);
}
export function Twemojify({ text }) {
return (
<>
{twemojify(text)}
</>
);
}
Twemojify.defaultProps = {
text: null,
};
Twemojify.propTypes = {
text: PropTypes.string,
};