Translation for chat pages

This commit is contained in:
Nicola Benaglia 2025-05-16 19:48:07 +02:00
parent 4f35730db6
commit cb336133c6
10 changed files with 120 additions and 52 deletions

View File

@ -1,4 +1,4 @@
import React, {
import {
useCallback,
useContext,
useEffect,
@ -50,6 +50,8 @@ import CloseIcon from '@mui/icons-material/Close';
import { throttle } from 'lodash';
import ImageIcon from '@mui/icons-material/Image';
import { messageHasImage } from '../../utils/chat';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 5 });
const uidImages = new ShortUniqueId({ length: 12 });
@ -75,8 +77,8 @@ export const ChatGroup = ({
const [isSending, setIsSending] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isMoved, setIsMoved] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null);
const hasInitialized = useRef(false);
const [isFocusedParent, setIsFocusedParent] = useState(false);
const [replyMessage, setReplyMessage] = useState(null);
@ -93,8 +95,8 @@ export const ChatGroup = ({
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const lastReadTimestamp = useRef(null);
const handleUpdateRef = useRef(null);
const { t } = useTranslation(['auth', 'core', 'group']);
const getTimestampEnterChat = async (selectedGroup) => {
try {
@ -129,7 +131,12 @@ export const ChatGroup = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || 'An error occurred');
rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
});
});
} catch (error) {
@ -154,9 +161,6 @@ export const ChatGroup = ({
return Array.from(uniqueMembers);
}, [messages]);
const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state
};
const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance;
};
@ -168,6 +172,7 @@ export const ChatGroup = ({
}
return [];
}, [selectedGroup, queueChats]);
const tempChatReferences = useMemo(() => {
if (!selectedGroup) return [];
if (queueChats[selectedGroup]) {
@ -184,12 +189,6 @@ export const ChatGroup = ({
}
}, [secretKey]);
// const getEncryptedSecretKey = useCallback(()=> {
// const response = getResource()
// const decryptResponse = decryptResource()
// return
// }, [])
const checkForFirstSecretKeyNotification = (messages) => {
messages?.forEach((message) => {
try {
@ -250,7 +249,6 @@ export const ChatGroup = ({
const dataRemovedBlock = responseData?.filter((item) => {
return !isUserBlocked(item?.sender, item?.senderName);
});
decryptMessages(dataRemovedBlock, false);
} catch (error) {
console.error(error);
@ -273,6 +271,7 @@ export const ChatGroup = ({
const filterUIMessages = encryptedMessages.filter(
(item) => !isExtMsg(item.data)
);
const decodedUIMessages =
decodeBase64ForUIChatMessages(filterUIMessages);
@ -280,6 +279,7 @@ export const ChatGroup = ({
...decodedUIMessages,
...response,
];
const combineUIAndExtensionMsgs = processWithNewMessages(
combineUIAndExtensionMsgsBefore.map((item) => ({
...item,
@ -287,16 +287,24 @@ export const ChatGroup = ({
})),
selectedGroup
);
res(combineUIAndExtensionMsgs);
if (isInitiated) {
const formatted = combineUIAndExtensionMsgs
.filter((rawItem) => !rawItem?.chatReference)
.map((item) => {
const message = (
<p>
{t('group:message.generic.group_key_created', {
postProcess: 'capitalizeFirst',
})}
</p>
);
const additionalFields =
item?.data === 'NDAwMQ=='
item?.data === 'NDAwMQ==' // TODO put magic string somewhere in a file
? {
text: '<p>First group key created.</p>',
text: message,
}
: {};
return {
@ -362,7 +370,9 @@ export const ChatGroup = ({
!newTimestamp
) {
console.warn(
'Invalid content, sender, or timestamp in reaction data',
t('group:message.generic.invalid_content', {
postProcess: 'capitalizeFirst',
}),
item
);
return;
@ -435,10 +445,17 @@ export const ChatGroup = ({
const formatted = combineUIAndExtensionMsgs
.filter((rawItem) => !rawItem?.chatReference)
.map((item) => {
const message = (
<p>
{t('group:message.generic.group_key_created', {
postProcess: 'capitalizeFirst',
})}
</p>
);
const additionalFields =
item?.data === 'NDAwMQ=='
? {
text: '<p>First group key created.</p>',
text: message,
}
: {};
const divide =
@ -510,7 +527,9 @@ export const ChatGroup = ({
!newTimestamp
) {
console.warn(
'Invalid content, sender, or timestamp in reaction data',
t('group:message.generic.invalid_content', {
postProcess: 'capitalizeFirst',
}),
item
);
return;
@ -583,7 +602,12 @@ export const ChatGroup = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || 'An error occurred');
rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
});
});
} catch (error) {
@ -615,6 +639,7 @@ export const ChatGroup = ({
console.error('Error during ping:', error);
}
};
const initWebsocketMessageGroup = () => {
let socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?txGroupId=${selectedGroup}&encoding=BASE64&limit=100`;
socketRef.current = new WebSocket(socketLink);
@ -709,7 +734,12 @@ export const ChatGroup = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || 'An error occurred');
rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
});
});
} catch (error) {
@ -744,7 +774,12 @@ export const ChatGroup = ({
rej(response.error);
})
.catch((error) => {
rej(error.message || 'An error occurred');
rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
});
});
} catch (error) {
@ -760,12 +795,22 @@ export const ChatGroup = ({
const sendMessage = async () => {
try {
if (messageSize > 4000) return;
if (messageSize > 4000) return; // TODO magic number
if (isPrivate === null)
throw new Error('Unable to determine if group is private');
throw new Error(
t('group:message.error.unable_determine_group_private', {
postProcess: 'capitalizeFirst',
})
);
if (isSending) return;
if (+balance < 4)
throw new Error('You need at least 4 QORT to send a message');
// TODO magic number
throw new Error(
t('group:message.error.qortals_required', {
quantity: 4,
postProcess: 'capitalizeFirst',
})
);
pauseAllQueues();
if (editorRef.current) {
const htmlContent = editorRef.current.getHTML();
@ -792,13 +837,16 @@ export const ChatGroup = ({
const imagesToPublish = [];
const deleteImage =
onEditMessage && isDeleteImage && messageHasImage(onEditMessage);
if (deleteImage) {
const fee = await getFee('ARBITRARY');
// TODO translate
await show({
publishFee: fee.fee + ' QORT',
message: 'Would you like to delete your previous chat image?',
});
// TODO magic string
await window.sendMessage('publishOnQDN', {
data: 'RA==',
identifier: onEditMessage?.images[0]?.identifier,
@ -811,6 +859,7 @@ export const ChatGroup = ({
const base64ToSave = isPrivate
? await encryptChatMessage(imageToSave, secretKeyObject)
: imageToSave;
// 1 represents public group, 0 is private
const identifier = `grp-q-manager_${isPrivate ? 0 : 1}_group_${selectedGroup}_${uidImages.rnd()}`;
imagesToPublish.push({
@ -822,7 +871,6 @@ export const ChatGroup = ({
const res = await window.sendMessage(
'PUBLISH_MULTIPLE_QDN_RESOURCES',
{
resources: imagesToPublish,
},
@ -979,6 +1027,7 @@ export const ChatGroup = ({
.setContent(message?.messageText || message?.text)
.run();
}, []);
const handleReaction = useCallback(
async (reaction, chatMessage, reactionState = true) => {
try {
@ -1033,12 +1082,6 @@ export const ChatGroup = ({
chatReference: chatMessage.signature,
};
addToQueue(sendMessageFunc, messageObj, 'chat-reaction', selectedGroup);
// setTimeout(() => {
// executeEvent("sent-new-message-group", {})
// }, 150);
// clearEditorContent()
// setReplyMessage(null)
// send chat message
} catch (error) {
const errorMsg = error?.message || error;
@ -1417,6 +1460,7 @@ export const ChatGroup = ({
}}
>
<Typography>Q-Manager</Typography>
<ButtonBase
onClick={() => {
setIsOpenQManager(false);
@ -1429,7 +1473,9 @@ export const ChatGroup = ({
/>
</ButtonBase>
</Box>
<Divider />
<AppViewerContainer
customHeight="560px"
app={{

View File

@ -202,11 +202,11 @@ export const ChatList = ({
ref={parentRef}
className="List"
style={{
display: 'flex',
flexGrow: 1,
height: '0px',
overflow: 'auto',
position: 'relative',
display: 'flex',
height: '0px',
}}
>
<div
@ -391,6 +391,7 @@ export const ChatList = ({
</div>
</div>
</div>
{showScrollButton && (
<button
onClick={() => scrollToBottom()}

View File

@ -387,6 +387,7 @@ export const ChatOptions = ({
}}
/>
</Box>
<Box
sx={{
alignItems: 'center',

View File

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import { useContext, useState } from 'react';
import { Box, Button, Typography, useTheme } from '@mui/material';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { LoadingButton } from '@mui/lab';
@ -34,9 +34,9 @@ export const CreateCommonSecret = ({
const { show } = useContext(MyContext);
const setTxList = useSetAtom(txListAtom);
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const theme = useTheme();

View File

@ -1,7 +1,9 @@
import React, {
import {
useCallback,
useContext,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react';
@ -140,9 +142,9 @@ export const GroupAnnouncements = ({
const [selectedAnnouncement, setSelectedAnnouncement] = useState(null);
const [isFocusedParent, setIsFocusedParent] = useState(false);
const { show } = React.useContext(MyContext);
const [openSnack, setOpenSnack] = React.useState(false);
const [infoSnack, setInfoSnack] = React.useState(null);
const { show } = useContext(MyContext);
const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null);
const hasInitialized = useRef(false);
const hasInitializedWebsocket = useRef(false);
const editorRef = useRef(null);
@ -150,12 +152,13 @@ export const GroupAnnouncements = ({
const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance;
};
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const { t } = useTranslation(['core', 'group']);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const { t } = useTranslation(['auth', 'core', 'group']);
const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state
};
useEffect(() => {
if (!selectedGroup) return;
(async () => {

View File

@ -77,8 +77,10 @@ export const GroupAvatar = ({
try {
if (!groupId) return;
const fee = await getFee('ARBITRARY');
if (+balance < +fee.fee)
throw new Error(`Publishing an Avatar requires ${fee.fee}`);
await show({
message: 'Would you like to publish an avatar?',
publishFee: fee.fee + ' QORT',
@ -132,6 +134,7 @@ export const GroupAvatar = ({
>
{myName?.charAt(0)}
</Avatar>
<ButtonBase onClick={handleChildClick}>
<Typography
sx={{
@ -142,6 +145,7 @@ export const GroupAvatar = ({
change avatar
</Typography>
</ButtonBase>
<PopoverComp
myName={myName}
avatarFile={avatarFile}
@ -170,6 +174,7 @@ export const GroupAvatar = ({
>
{myName?.charAt(0)}
</Avatar>
<ButtonBase onClick={handleChildClick}>
<Typography
sx={{
@ -180,6 +185,7 @@ export const GroupAvatar = ({
change avatar
</Typography>
</ButtonBase>
<PopoverComp
myName={myName}
avatarFile={avatarFile}
@ -208,6 +214,7 @@ export const GroupAvatar = ({
set avatar
</Typography>
</ButtonBase>
<PopoverComp
myName={myName}
avatarFile={avatarFile}
@ -258,11 +265,15 @@ const PopoverComp = ({
>
(500 KB max. for GIFS){' '}
</Typography>
<ImageUploader onPick={(file) => setAvatarFile(file)}>
<Button variant="contained">Choose Image</Button>
</ImageUploader>
{avatarFile?.name}
<Spacer height="25px" />
{!myName && (
<Box
sx={{
@ -283,6 +294,7 @@ const PopoverComp = ({
)}
<Spacer height="25px" />
<LoadingButton
loading={isLoading}
disabled={!avatarFile || !myName}

View File

@ -171,8 +171,9 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
}
}
} catch (error) {
//error
console.log(error);
}
const res = extractComponents(url);
if (res) {
const { service, name, identifier, path } = res;

View File

@ -1,4 +1,5 @@
import React, {
import {
memo,
useCallback,
useContext,
useEffect,
@ -77,7 +78,7 @@ const getBadgeImg = (level) => {
}
};
const UserBadge = React.memo(({ userInfo }) => {
const UserBadge = memo(({ userInfo }) => {
return (
<Tooltip disableFocusListener title={`level ${userInfo ?? 0}`}>
<img
@ -92,7 +93,7 @@ const UserBadge = React.memo(({ userInfo }) => {
);
});
export const MessageItem = React.memo(
export const MessageItem = memo(
({
message,
onSeen,

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
import { EditorProvider, useCurrentEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { Color } from '@tiptap/extension-color';
@ -42,7 +42,7 @@ function textMatcher(doc, from) {
return { start, query };
}
const MenuBar = React.memo(
const MenuBar = memo(
({
setEditorRef,
isChat,

View File

@ -61,6 +61,8 @@
"descrypt_wallet": "decrypting wallet...",
"encryption_key": "the group's first common encryption key is in the process of creation. Please wait a few minutes for it to be retrieved by the network. Checking every 2 minutes...",
"group_invited_you": "{{group}} has invited you",
"group_key_created": "first group key created.",
"invalid_content": "invalid content, sender, or timestamp in reaction data",
"invalid_data": "error loading content: Invalid Data",
"latest_promotion": "only the latest promotion from the week will be shown for your group.",
"loading_members": "loading member list with names... please wait.",
@ -97,6 +99,7 @@
"qortals_required": "you need at least {{ quantity }} QORT to send a message",
"timeout_reward": "timeout waiting for reward share confirmation",
"thread_id": "unable to locate thread Id",
"unable_determine_group_private": "unable to determine if group is private",
"unable_minting": "unable to start minting"
},
"success": {