Merge remote-tracking branch 'qortal/develop' into feature/large-files-and-names

This commit is contained in:
PhilReact 2025-05-17 23:51:56 +03:00
commit 109b71689e
152 changed files with 6930 additions and 4462 deletions

View File

@ -1,10 +1,25 @@
# I18N Guidelines # I18N Guidelines
In JSON file: [react-i18next](https://react.i18next.com/) is the framework used for internationalization.
## Locales
Locales are in folder `./src/i18n/locales`, one folder per language.
A single JSON file represents a namespace (group of translation).
It's a key/value structure.
Please:
- Keep the file sorted - Keep the file sorted
- Always write in lowercase - First letter of each value is lowercase
In GUI: Translation in GUI:
- If the first letter of the translation must be uppercase, use the postProcess, for example: `{t_auth('advanced_users', { postProcess: 'capitalize' })}` - If the first letter of the translation must be uppercase, use the postProcess, for example: `t('core:advanced_users', { postProcess: 'capitalizeFirst' })`
- For all translation in uppercase `{ postProcess: 'capitalizeAll' }`
- See `.src/i18n/i18n.ts` for processor definition
## Missing language?
- Please open an issue on the project's github repository and specify the missing language

File diff suppressed because it is too large Load Diff

View File

@ -351,7 +351,8 @@ export const NotAuthenticated = ({
.catch((error) => { .catch((error) => {
console.error( console.error(
'Failed to set API key:', 'Failed to set API key:',
error.message || t('core:error', { postProcess: 'capitalize' }) error.message ||
t('core:error', { postProcess: 'capitalizeFirst' })
); );
}); });
} else { } else {
@ -361,7 +362,7 @@ export const NotAuthenticated = ({
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: t('auth:apikey.select_valid', { message: t('auth:apikey.select_valid', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -387,7 +388,7 @@ export const NotAuthenticated = ({
'Failed to set API key:', 'Failed to set API key:',
error.message || error.message ||
t('core:error', { t('core:error', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
); );
}); });
@ -399,7 +400,7 @@ export const NotAuthenticated = ({
message: message:
error?.message || error?.message ||
t('auth:apikey.select_valid', { t('auth:apikey.select_valid', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -481,7 +482,7 @@ export const NotAuthenticated = ({
fontSize: '18px', fontSize: '18px',
}} }}
> >
{t('auth:welcome', { postProcess: 'capitalize' })} {t('auth:welcome', { postProcess: 'capitalizeFirst' })}
<TextSpan <TextSpan
sx={{ sx={{
fontSize: '18px', fontSize: '18px',
@ -511,13 +512,13 @@ export const NotAuthenticated = ({
fontSize: '16px', fontSize: '16px',
}} }}
> >
{t('auth:tips.digital_id', { postProcess: 'capitalize' })} {t('auth:tips.digital_id', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</React.Fragment> </React.Fragment>
} }
> >
<CustomButton onClick={() => setExtstate('wallets')}> <CustomButton onClick={() => setExtstate('wallets')}>
{t('auth:account.account_many', { postProcess: 'capitalize' })} {t('auth:account.account_many', { postProcess: 'capitalizeFirst' })}
</CustomButton> </CustomButton>
</HtmlTooltip> </HtmlTooltip>
</Box> </Box>
@ -542,7 +543,7 @@ export const NotAuthenticated = ({
fontSize: '18px', fontSize: '18px',
}} }}
> >
{t('auth:tips.new_users', { postProcess: 'capitalize' })} {t('auth:tips.new_users', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
<Spacer height="10px" /> <Spacer height="10px" />
<Typography <Typography
@ -551,7 +552,7 @@ export const NotAuthenticated = ({
fontSize: '16px', fontSize: '16px',
}} }}
> >
{t('auth:tips.new_account', { postProcess: 'capitalize' })} {t('auth:tips.new_account', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</React.Fragment> </React.Fragment>
} }
@ -572,7 +573,9 @@ export const NotAuthenticated = ({
}, },
}} }}
> >
{t('auth:create_account', { postProcess: 'capitalize' })} {t('auth:action.create_account', {
postProcess: 'capitalizeFirst',
})}
</CustomButton> </CustomButton>
</HtmlTooltip> </HtmlTooltip>
</Box> </Box>
@ -585,7 +588,7 @@ export const NotAuthenticated = ({
visibility: !useLocalNode && 'hidden', visibility: !useLocalNode && 'hidden',
}} }}
> >
{t('auth:node.using', { postProcess: 'capitalize' })}:{' '} {t('auth:node.using', { postProcess: 'capitalizeFirst' })}:{' '}
{currentNode?.url} {currentNode?.url}
</Typography> </Typography>
@ -613,7 +616,7 @@ export const NotAuthenticated = ({
textDecoration: 'underline', textDecoration: 'underline',
}} }}
> >
{t('auth:advanced_users', { postProcess: 'capitalize' })} {t('auth:advanced_users', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
<Box <Box
sx={{ sx={{
@ -662,8 +665,12 @@ export const NotAuthenticated = ({
} }
label={ label={
isLocal isLocal
? t('auth:node.use_local', { postProcess: 'capitalize' }) ? t('auth:node.use_local', {
: t('auth:node.use_custom', { postProcess: 'capitalize' }) postProcess: 'capitalizeFirst',
})
: t('auth:node.use_custom', {
postProcess: 'capitalizeFirst',
})
} }
/> />
</Box> </Box>
@ -676,8 +683,12 @@ export const NotAuthenticated = ({
component="label" component="label"
> >
{apiKey {apiKey
? t('auth:node.use_local', { postProcess: 'capitalize' }) ? t('auth:node.use_local', {
: t('auth:apikey.import', { postProcess: 'capitalize' })} postProcess: 'capitalizeFirst',
})
: t('auth:apikey.import', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Typography <Typography
sx={{ sx={{
@ -685,7 +696,7 @@ export const NotAuthenticated = ({
visibility: importedApiKey ? 'visible' : 'hidden', visibility: importedApiKey ? 'visible' : 'hidden',
}} }}
> >
{t('auth:apikey.key', { postProcess: 'capitalize' })}:{' '} {t('auth:apikey.key', { postProcess: 'capitalizeFirst' })}:{' '}
{importedApiKey} {importedApiKey}
</Typography> </Typography>
</> </>
@ -698,7 +709,7 @@ export const NotAuthenticated = ({
variant="contained" variant="contained"
component="label" component="label"
> >
{t('auth:node.choose', { postProcess: 'capitalize' })} {t('auth:node.choose', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</> </>
<Typography <Typography
@ -707,7 +718,7 @@ export const NotAuthenticated = ({
fontSize: '12px', fontSize: '12px',
}} }}
> >
{t('auth:build_version', { postProcess: 'capitalize' })}: {t('auth:build_version', { postProcess: 'capitalizeFirst' })}:
{manifestData?.version} {manifestData?.version}
</Typography> </Typography>
</Box> </Box>
@ -728,7 +739,7 @@ export const NotAuthenticated = ({
> >
<DialogTitle id="alert-dialog-title"> <DialogTitle id="alert-dialog-title">
{' '} {' '}
{t('auth:node.custom_many', { postProcess: 'capitalize' })}: {t('auth:node.custom_many', { postProcess: 'capitalizeFirst' })}:
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box <Box
@ -798,7 +809,9 @@ export const NotAuthenticated = ({
}} }}
variant="contained" variant="contained"
> >
{t('core:action.choose', { postProcess: 'capitalize' })} {t('core:action.choose', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
</Box> </Box>
</Box> </Box>
@ -858,7 +871,7 @@ export const NotAuthenticated = ({
variant="contained" variant="contained"
> >
{t('core:action.choose', { {t('core:action.choose', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Button> </Button>
@ -873,7 +886,7 @@ export const NotAuthenticated = ({
variant="contained" variant="contained"
> >
{t('core:action.edit', { {t('core:action.edit', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Button> </Button>
@ -887,7 +900,9 @@ export const NotAuthenticated = ({
}} }}
variant="contained" variant="contained"
> >
{t('core:remove', { postProcess: 'capitalize' })} {t('core:remove', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
</Box> </Box>
</Box> </Box>
@ -926,7 +941,7 @@ export const NotAuthenticated = ({
<DialogActions> <DialogActions>
{mode === 'list' && ( {mode === 'list' && (
<Button variant="contained" onClick={addCustomNode}> <Button variant="contained" onClick={addCustomNode}>
{t('core:action.add', { postProcess: 'capitalize' })} {t('core:action.add', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
)} )}
@ -939,7 +954,7 @@ export const NotAuthenticated = ({
}} }}
autoFocus autoFocus
> >
{t('core:action.close', { postProcess: 'capitalize' })} {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</> </>
)} )}
@ -953,7 +968,9 @@ export const NotAuthenticated = ({
setCustomNodeToSaveIndex(null); setCustomNodeToSaveIndex(null);
}} }}
> >
{t('auth:return_to_list', { postProcess: 'capitalize' })} {t('auth:action.return_to_list', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Button <Button
@ -962,7 +979,7 @@ export const NotAuthenticated = ({
onClick={() => saveCustomNodes(customNodes)} onClick={() => saveCustomNodes(customNodes)}
autoFocus autoFocus
> >
{t('core:save', { postProcess: 'capitalize' })} {t('core:save', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</> </>
)} )}
@ -977,7 +994,7 @@ export const NotAuthenticated = ({
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle id="alert-dialog-title"> <DialogTitle id="alert-dialog-title">
{t('auth:apikey.enter', { postProcess: 'capitalize' })} {t('auth:apikey.enter', { postProcess: 'capitalizeFirst' })}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box <Box
@ -996,7 +1013,9 @@ export const NotAuthenticated = ({
variant="contained" variant="contained"
component="label" component="label"
> >
{t('auth:apikey.alternative', { postProcess: 'capitalize' })} {t('auth:apikey.alternative', {
postProcess: 'capitalizeFirst',
})}
<input <input
type="file" type="file"
accept=".txt" accept=".txt"
@ -1051,7 +1070,7 @@ export const NotAuthenticated = ({
}} }}
autoFocus autoFocus
> >
{t('core:save', { postProcess: 'capitalize' })} {t('core:save', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -1061,7 +1080,7 @@ export const NotAuthenticated = ({
setShowSelectApiKey(false); setShowSelectApiKey(false);
}} }}
> >
{t('core:action.close', { postProcess: 'capitalize' })} {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -1,12 +1,17 @@
import React, { createContext, useContext, useState, useCallback, useRef } from 'react'; import {
import ShortUniqueId from "short-unique-id"; createContext,
useContext,
useState,
useCallback,
useRef,
} from 'react';
import ShortUniqueId from 'short-unique-id';
const MessageQueueContext = createContext(null); const MessageQueueContext = createContext(null);
const uid = new ShortUniqueId({ length: 8 });
export const useMessageQueue = () => useContext(MessageQueueContext); export const useMessageQueue = () => useContext(MessageQueueContext);
const uid = new ShortUniqueId({ length: 8 });
export const MessageQueueProvider = ({ children }) => { export const MessageQueueProvider = ({ children }) => {
const messageQueueRef = useRef([]); const messageQueueRef = useRef([]);
const [queueChats, setQueueChats] = useState({}); // Stores chats and status for display const [queueChats, setQueueChats] = useState({}); // Stores chats and status for display
@ -21,39 +26,41 @@ export const MessageQueueProvider = ({ children }) => {
const processingPromiseRef = useRef(Promise.resolve()); const processingPromiseRef = useRef(Promise.resolve());
// Function to add a message to the queue // Function to add a message to the queue
const addToQueue = useCallback((sendMessageFunc, messageObj, type, groupDirectId) => { const addToQueue = useCallback(
const tempId = uid.rnd(); (sendMessageFunc, messageObj, type, groupDirectId) => {
const chatData = { const tempId = uid.rnd();
...messageObj, const chatData = {
type, ...messageObj,
groupDirectId, type,
signature: uid.rnd(), groupDirectId,
identifier: tempId, signature: uid.rnd(),
retries: 0, // Retry count for display purposes identifier: tempId,
status: 'pending' // Initial status is 'pending' retries: 0, // Retry count for display purposes
}; status: 'pending', // Initial status is 'pending'
};
// Add chat data to queueChats for status tracking // Add chat data to queueChats for status tracking
setQueueChats((prev) => ({ setQueueChats((prev) => ({
...prev, ...prev,
[groupDirectId]: [...(prev[groupDirectId] || []), chatData] [groupDirectId]: [...(prev[groupDirectId] || []), chatData],
})); }));
// Add the message to the global messageQueueRef // Add the message to the global messageQueueRef
messageQueueRef.current.push({ messageQueueRef.current.push({
func: sendMessageFunc, func: sendMessageFunc,
identifier: tempId, identifier: tempId,
groupDirectId, groupDirectId,
specialId: messageObj?.message?.specialId specialId: messageObj?.message?.specialId,
}); });
// Start processing the queue // Start processing the queue
processQueue([], groupDirectId); processQueue([], groupDirectId);
}, []); },
[]
);
// Function to process the message queue // Function to process the message queue
const processQueue = useCallback((newMessages = [], groupDirectId) => { const processQueue = useCallback((newMessages = [], groupDirectId) => {
processingPromiseRef.current = processingPromiseRef.current processingPromiseRef.current = processingPromiseRef.current
.then(() => processQueueInternal(newMessages, groupDirectId)) .then(() => processQueueInternal(newMessages, groupDirectId))
.catch((err) => console.error('Error in processQueue:', err)); .catch((err) => console.error('Error in processQueue:', err));
@ -92,7 +99,6 @@ export const MessageQueueProvider = ({ children }) => {
// Remove the message from the queue after successful sending // Remove the message from the queue after successful sending
messageQueueRef.current.shift(); messageQueueRef.current.shift();
} catch (error) { } catch (error) {
console.error('Message sending failed', error); console.error('Message sending failed', error);
@ -110,7 +116,8 @@ export const MessageQueueProvider = ({ children }) => {
updatedChats[groupDirectId][chatIndex].status = 'failed'; updatedChats[groupDirectId][chatIndex].status = 'failed';
} else { } else {
// Max retries reached, set status to 'failed-permanent' // Max retries reached, set status to 'failed-permanent'
updatedChats[groupDirectId][chatIndex].status = 'failed-permanent'; updatedChats[groupDirectId][chatIndex].status =
'failed-permanent';
// Remove the message from the queue after max retries // Remove the message from the queue after max retries
messageQueueRef.current.shift(); messageQueueRef.current.shift();
@ -127,33 +134,39 @@ export const MessageQueueProvider = ({ children }) => {
// Method to process with new messages and groupDirectId // Method to process with new messages and groupDirectId
const processWithNewMessages = (newMessages, groupDirectId) => { const processWithNewMessages = (newMessages, groupDirectId) => {
let updatedNewMessages = newMessages let updatedNewMessages = newMessages;
if (newMessages.length > 0) { if (newMessages.length > 0) {
// Remove corresponding entries in queueChats for the provided groupDirectId // Remove corresponding entries in queueChats for the provided groupDirectId
setQueueChats((prev) => { setQueueChats((prev) => {
const updatedChats = { ...prev }; const updatedChats = { ...prev };
if (updatedChats[groupDirectId]) { if (updatedChats[groupDirectId]) {
updatedNewMessages = newMessages?.map((msg) => {
updatedNewMessages = newMessages?.map((msg)=> { const findTempMsg = updatedChats[groupDirectId]?.find(
const findTempMsg = updatedChats[groupDirectId]?.find((msg2)=> msg2?.message?.specialId === msg?.specialId) (msg2) => msg2?.message?.specialId === msg?.specialId
if(findTempMsg){ );
if (findTempMsg) {
return { return {
...msg, ...msg,
tempSignature: findTempMsg?.signature tempSignature: findTempMsg?.signature,
} };
} }
return msg return msg;
})
updatedChats[groupDirectId] = updatedChats[groupDirectId].filter((chat) => {
return !newMessages.some(newMsg => newMsg?.specialId === chat?.message?.specialId);
}); });
updatedChats[groupDirectId] = updatedChats[groupDirectId].filter(
(chat) => {
return !newMessages.some(
(newMsg) => newMsg?.specialId === chat?.message?.specialId
);
}
);
// Remove messages with status 'failed-permanent' // Remove messages with status 'failed-permanent'
updatedChats[groupDirectId] = updatedChats[groupDirectId].filter((chat) => { updatedChats[groupDirectId] = updatedChats[groupDirectId].filter(
return chat?.status !== 'failed-permanent'; (chat) => {
}); return chat?.status !== 'failed-permanent';
}
);
// If no more chats for this group, delete the groupDirectId entry // If no more chats for this group, delete the groupDirectId entry
if (updatedChats[groupDirectId].length === 0) { if (updatedChats[groupDirectId].length === 0) {
@ -162,27 +175,36 @@ export const MessageQueueProvider = ({ children }) => {
} }
return updatedChats; return updatedChats;
}); });
} }
setTimeout(() => { setTimeout(() => {
if(!messageQueueRef.current.find((msg) => msg?.groupDirectId === groupDirectId)){ if (
!messageQueueRef.current.find(
(msg) => msg?.groupDirectId === groupDirectId
)
) {
setQueueChats((prev) => { setQueueChats((prev) => {
const updatedChats = { ...prev }; const updatedChats = { ...prev };
if (updatedChats[groupDirectId]) { if (updatedChats[groupDirectId]) {
delete updatedChats[groupDirectId] delete updatedChats[groupDirectId];
} }
return updatedChats return updatedChats;
} });
)
} }
}, 300); }, 300);
return updatedNewMessages return updatedNewMessages;
}; };
return ( return (
<MessageQueueContext.Provider value={{ addToQueue, queueChats, clearStatesMessageQueueProvider, processWithNewMessages }}> <MessageQueueContext.Provider
value={{
addToQueue,
queueChats,
clearStatesMessageQueueProvider,
processWithNewMessages,
}}
>
{children} {children}
</MessageQueueContext.Provider> </MessageQueueContext.Provider>
); );

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react'; import { Fragment, useContext, useEffect, useState } from 'react';
import List from '@mui/material/List'; import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem'; import ListItem from '@mui/material/ListItem';
import Divider from '@mui/material/Divider'; import Divider from '@mui/material/Divider';
@ -32,6 +32,7 @@ import { LoadingButton } from '@mui/lab';
import { PasswordField } from './components'; import { PasswordField } from './components';
import { HtmlTooltip } from './ExtStates/NotAuthenticated'; import { HtmlTooltip } from './ExtStates/NotAuthenticated';
import { MyContext } from './App'; import { MyContext } from './App';
import { useTranslation } from 'react-i18next';
const parsefilenameQortal = (filename) => { const parsefilenameQortal = (filename) => {
return filename.startsWith('qortal_backup_') ? filename.slice(14) : filename; return filename.startsWith('qortal_backup_') ? filename.slice(14) : filename;
@ -44,11 +45,11 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
const [seedName, setSeedName] = useState(''); const [seedName, setSeedName] = useState('');
const [seedError, setSeedError] = useState(''); const [seedError, setSeedError] = useState('');
const { hasSeenGettingStarted } = useContext(MyContext); const { hasSeenGettingStarted } = useContext(MyContext);
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [isOpenSeedModal, setIsOpenSeedModal] = useState(false); const [isOpenSeedModal, setIsOpenSeedModal] = useState(false);
const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false); const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'auth']);
const { isShow, onCancel, onOk, show } = useModal(); const { isShow, onCancel, onOk, show } = useModal();
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
@ -82,8 +83,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
} }
} }
let error: any = null; const uniqueInitialMap = new Map();
let uniqueInitialMap = new Map();
// Only add a message if it doesn't already exist in the Map // Only add a message if it doesn't already exist in the Map
importedWallets.forEach((wallet) => { importedWallets.forEach((wallet) => {
@ -92,7 +92,9 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
uniqueInitialMap.set(wallet?.address0, wallet); uniqueInitialMap.set(wallet?.address0, wallet);
} }
}); });
const data = Array.from(uniqueInitialMap.values()); const data = Array.from(uniqueInitialMap.values());
if (data && data?.length > 0) { if (data && data?.length > 0) {
const uniqueNewWallets = data.filter( const uniqueNewWallets = data.filter(
(newWallet) => (newWallet) =>
@ -148,10 +150,19 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
setPassword(''); setPassword('');
setSeedError(''); setSeedError('');
} else { } else {
setSeedError('Could not create account.'); setSeedError(
t('auth:message.error.account_creation', {
postProcess: 'capitalizeFirst',
})
);
} }
} catch (error) { } catch (error) {
setSeedError(error?.message || 'Could not create account.'); setSeedError(
error?.message ||
t('auth:message.error.account_creation', {
postProcess: 'capitalizeFirst',
})
);
} finally { } finally {
setIsLoadingEncryptSeed(false); setIsLoadingEncryptSeed(false);
} }
@ -189,19 +200,34 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
<div> <div>
{wallets?.length === 0 || !wallets ? ( {wallets?.length === 0 || !wallets ? (
<> <>
<Typography>No accounts saved</Typography> <Typography>
{t('auth:message.generic.no_account', {
postProcess: 'capitalizeFirst',
})}
</Typography>
<Spacer height="75px" /> <Spacer height="75px" />
</> </>
) : ( ) : (
<> <>
<Typography>Your saved accounts</Typography> <Typography>
{t('auth:message.generic.your_accounts', {
postProcess: 'capitalizeFirst',
})}
</Typography>
<Spacer height="30px" /> <Spacer height="30px" />
</> </>
)} )}
{rawWallet && ( {rawWallet && (
<Box> <Box>
<Typography>Selected Account:</Typography> <Typography>
{t('auth:account.selected', {
postProcess: 'capitalizeFirst',
})}
:
</Typography>
{rawWallet?.name && <Typography>{rawWallet.name}</Typography>} {rawWallet?.name && <Typography>{rawWallet.name}</Typography>}
{rawWallet?.address0 && ( {rawWallet?.address0 && (
<Typography>{rawWallet?.address0}</Typography> <Typography>{rawWallet?.address0}</Typography>
@ -211,12 +237,12 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
{wallets?.length > 0 && ( {wallets?.length > 0 && (
<List <List
sx={{ sx={{
width: '100%',
maxWidth: '500px',
maxHeight: '60vh',
overflowY: 'auto',
overflowX: 'hidden',
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
maxHeight: '60vh',
maxWidth: '500px',
overflowX: 'hidden',
overflowY: 'auto',
width: '100%',
}} }}
> >
{wallets?.map((wallet, idx) => { {wallets?.map((wallet, idx) => {
@ -238,29 +264,29 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
<Box <Box
sx={{ sx={{
alignItems: 'center',
bottom: wallets?.length === 0 ? 'unset' : '20px',
display: 'flex', display: 'flex',
gap: '10px', gap: '10px',
alignItems: 'center',
position: wallets?.length === 0 ? 'relative' : 'fixed', position: wallets?.length === 0 ? 'relative' : 'fixed',
bottom: wallets?.length === 0 ? 'unset' : '20px',
right: wallets?.length === 0 ? 'unset' : '20px', right: wallets?.length === 0 ? 'unset' : '20px',
}} }}
> >
<HtmlTooltip <HtmlTooltip
disableHoverListener={hasSeenGettingStarted === true} disableHoverListener={hasSeenGettingStarted === true}
title={ title={
<React.Fragment> <Fragment>
<Typography <Typography
color="inherit" color="inherit"
sx={{ sx={{
fontSize: '16px', fontSize: '16px',
}} }}
> >
Already have a Qortal account? Enter your secret backup phrase {t('auth:tips.existing_account', {
here to access it. This phrase is one of the ways to recover postProcess: 'capitalizeFirst',
your account. })}
</Typography> </Typography>
</React.Fragment> </Fragment>
} }
> >
<CustomButton <CustomButton
@ -270,25 +296,27 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
display: 'inline', display: 'inline',
}} }}
> >
Add seed-phrase {t('auth:action.add.seed_phrase', {
postProcess: 'capitalizeFirst',
})}
</CustomButton> </CustomButton>
</HtmlTooltip> </HtmlTooltip>
<HtmlTooltip <HtmlTooltip
disableHoverListener={hasSeenGettingStarted === true} disableHoverListener={hasSeenGettingStarted === true}
title={ title={
<React.Fragment> <Fragment>
<Typography <Typography
color="inherit" color="inherit"
sx={{ sx={{
fontSize: '16px', fontSize: '16px',
}} }}
> >
Use this option to connect additional Qortal wallets you've {t('auth:tips.additional_wallet', {
already made, in order to login with them afterwards. You will postProcess: 'capitalizeFirst',
need access to your backup JSON file in order to do so. })}
</Typography> </Typography>
</React.Fragment> </Fragment>
} }
> >
<CustomButton <CustomButton
@ -298,7 +326,9 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
{...getRootProps()} {...getRootProps()}
> >
<input {...getInputProps()} /> <input {...getInputProps()} />
Add account {t('auth:action.add.account', {
postProcess: 'capitalizeFirst',
})}
</CustomButton> </CustomButton>
</HtmlTooltip> </HtmlTooltip>
</Box> </Box>
@ -314,7 +344,9 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
}} }}
> >
<DialogTitle id="alert-dialog-title"> <DialogTitle id="alert-dialog-title">
Type or paste in your seed-phrase {t('auth:message.generic.type_seed', {
postProcess: 'capitalizeFirst',
})}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
@ -324,7 +356,11 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
flexDirection: 'column', flexDirection: 'column',
}} }}
> >
<Label>Name</Label> <Label>
{t('core:name', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Input <Input
placeholder="Name" placeholder="Name"
value={seedName} value={seedName}
@ -333,7 +369,11 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
<Spacer height="7px" /> <Spacer height="7px" />
<Label>Seed-phrase</Label> <Label>
{t('auth:seed', {
postProcess: 'capitalizeFirst',
})}
</Label>
<PasswordField <PasswordField
placeholder="Seed-phrase" placeholder="Seed-phrase"
id="standard-adornment-password" id="standard-adornment-password"
@ -347,7 +387,11 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
<Spacer height="7px" /> <Spacer height="7px" />
<Label>Choose new password</Label> <Label>
{t('auth:action.choose_password', {
postProcess: 'capitalizeFirst',
})}
</Label>
<PasswordField <PasswordField
id="standard-adornment-password" id="standard-adornment-password"
value={password} value={password}
@ -359,6 +403,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
/> />
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
disabled={isLoadingEncryptSeed} disabled={isLoadingEncryptSeed}
@ -371,7 +416,9 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
setSeedError(''); setSeedError('');
}} }}
> >
Close {t('core:action.close', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<LoadingButton <LoadingButton
loading={isLoadingEncryptSeed} loading={isLoadingEncryptSeed}
@ -383,7 +430,9 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
}} }}
autoFocus autoFocus
> >
Add {t('core:action.add', {
postProcess: 'capitalizeFirst',
})}
</LoadingButton> </LoadingButton>
<Typography <Typography
sx={{ sx={{
@ -404,6 +453,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
const [note, setNote] = useState(''); const [note, setNote] = useState('');
const [isEdit, setIsEdit] = useState(false); const [isEdit, setIsEdit] = useState(false);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'auth']);
useEffect(() => { useEffect(() => {
if (wallet?.name) { if (wallet?.name) {
@ -439,6 +489,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
<ListItemAvatar> <ListItemAvatar>
<Avatar alt="" src="/static/images/avatar/1.jpg" /> <Avatar alt="" src="/static/images/avatar/1.jpg" />
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={ primary={
wallet?.name wallet?.name
@ -468,7 +519,9 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
marginTop: '5px', marginTop: '5px',
}} }}
> >
Login {t('core:action.login', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
} }
@ -495,7 +548,11 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
padding: '8px', padding: '8px',
}} }}
> >
<Label>Name</Label> <Label>
{t('core:name', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Input <Input
placeholder="Name" placeholder="Name"
value={name} value={name}
@ -507,7 +564,11 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
<Spacer height="10px" /> <Spacer height="10px" />
<Label>Note</Label> <Label>
{t('auth:note', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Input <Input
placeholder="Note" placeholder="Note"
value={note} value={note}
@ -535,7 +596,9 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
variant="contained" variant="contained"
onClick={() => setIsEdit(false)} onClick={() => setIsEdit(false)}
> >
Close {t('core:action.close', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Button <Button
sx={{ sx={{
@ -551,7 +614,9 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
variant="contained" variant="contained"
onClick={() => updateWalletItem(idx, null)} onClick={() => updateWalletItem(idx, null)}
> >
Remove {t('core:action.remove', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Button <Button
sx={{ sx={{
@ -574,7 +639,9 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
setIsEdit(false); setIsEdit(false);
}} }}
> >
Save {t('core:action.save', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
</Box> </Box>
</Box> </Box>

View File

@ -18,37 +18,36 @@ export const sortablePinnedAppsAtom = atomWithReset([
{ name: 'Q-Nodecontrol', service: 'APP' }, { name: 'Q-Nodecontrol', service: 'APP' },
]); ]);
export const canSaveSettingToQdnAtom = atomWithReset(false);
export const settingsQDNLastUpdatedAtom = atomWithReset(-100);
export const settingsLocalLastUpdatedAtom = atomWithReset(0);
export const oldPinnedAppsAtom = atomWithReset([]);
export const isUsingImportExportSettingsAtom = atomWithReset(null);
export const fullScreenAtom = atomWithReset(false);
export const hasSettingsChangedAtom = atomWithReset(false);
export const navigationControllerAtom = atomWithReset({});
export const enabledDevModeAtom = atomWithReset(false);
export const myGroupsWhereIAmAdminAtom = atomWithReset([]);
export const promotionTimeIntervalAtom = atomWithReset(0);
export const promotionsAtom = atomWithReset([]);
export const resourceDownloadControllerAtom = atomWithReset({});
export const blobControllerAtom = atomWithReset({});
export const selectedGroupIdAtom = atomWithReset(null);
export const addressInfoControllerAtom = atomWithReset({}); export const addressInfoControllerAtom = atomWithReset({});
export const blobControllerAtom = atomWithReset({});
export const canSaveSettingToQdnAtom = atomWithReset(false);
export const enabledDevModeAtom = atomWithReset(false);
export const fullScreenAtom = atomWithReset(false);
export const groupAnnouncementsAtom = atomWithReset({});
export const groupChatTimestampsAtom = atomWithReset({});
export const groupsOwnerNamesAtom = atomWithReset({});
export const groupsPropertiesAtom = atomWithReset({});
export const hasSettingsChangedAtom = atomWithReset(false);
export const isDisabledEditorEnterAtom = atomWithReset(false); export const isDisabledEditorEnterAtom = atomWithReset(false);
export const qMailLastEnteredTimestampAtom = atomWithReset(null); export const isOpenBlockedModalAtom = atomWithReset(false);
export const isRunningPublicNodeAtom = atomWithReset(false);
export const isUsingImportExportSettingsAtom = atomWithReset(null);
export const lastPaymentSeenTimestampAtom = atomWithReset(null); export const lastPaymentSeenTimestampAtom = atomWithReset(null);
export const mailsAtom = atomWithReset([]); export const mailsAtom = atomWithReset([]);
export const groupsPropertiesAtom = atomWithReset({});
export const groupsOwnerNamesAtom = atomWithReset({});
export const isOpenBlockedModalAtom = atomWithReset(false);
export const groupAnnouncementsAtom = atomWithReset({});
export const mutedGroupsAtom = atomWithReset([]);
export const groupChatTimestampsAtom = atomWithReset({});
export const timestampEnterDataAtom = atomWithReset({});
export const txListAtom = atomWithReset([]);
export const memberGroupsAtom = atomWithReset([]); export const memberGroupsAtom = atomWithReset([]);
export const isRunningPublicNodeAtom = atomWithReset(false); export const mutedGroupsAtom = atomWithReset([]);
export const myGroupsWhereIAmAdminAtom = atomWithReset([]);
export const navigationControllerAtom = atomWithReset({});
export const oldPinnedAppsAtom = atomWithReset([]);
export const promotionsAtom = atomWithReset([]);
export const promotionTimeIntervalAtom = atomWithReset(0);
export const qMailLastEnteredTimestampAtom = atomWithReset(null);
export const resourceDownloadControllerAtom = atomWithReset({});
export const selectedGroupIdAtom = atomWithReset(null);
export const settingsLocalLastUpdatedAtom = atomWithReset(0);
export const settingsQDNLastUpdatedAtom = atomWithReset(-100);
export const timestampEnterDataAtom = atomWithReset({});
export const txListAtom = atomWithReset([]);
// Atom Families (replacing selectorFamily) // Atom Families (replacing selectorFamily)
export const resourceKeySelector = atomFamily((key) => export const resourceKeySelector = atomFamily((key) =>

View File

@ -52,7 +52,6 @@ import {
sendChatGroup, sendChatGroup,
sendChatNotification, sendChatNotification,
sendCoin, sendCoin,
setChatHeads,
setGroupData, setGroupData,
updateThreadActivity, updateThreadActivity,
walletVersion, walletVersion,
@ -104,7 +103,7 @@ export async function getWalletInfoCase(request, event) {
{ {
requestId: request.requestId, requestId: request.requestId,
action: 'getWalletInfo', action: 'getWalletInfo',
error: 'No wallet info found', error: 'No wallet info found', // TODO translate
type: 'backgroundMessageResponse', type: 'backgroundMessageResponse',
}, },
event.origin event.origin

View File

@ -1,4 +1,3 @@
import React, { useEffect, useMemo, useState } from 'react';
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
@ -28,8 +27,8 @@ import {
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from '../../atoms/global'; } from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop'; import { saveToLocalStorage } from './AppsNavBarDesktop';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
export const AppInfo = ({ app, myName }) => { export const AppInfo = ({ app, myName }) => {
const isInstalled = app?.status?.status === 'READY'; const isInstalled = app?.status?.status === 'READY';
@ -38,6 +37,7 @@ export const AppInfo = ({ app, myName }) => {
); );
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'auth', 'group']);
const isSelectedAppPinned = !!sortablePinnedApps?.find( const isSelectedAppPinned = !!sortablePinnedApps?.find(
(item) => item?.name === app?.name && item?.service === app?.service (item) => item?.name === app?.name && item?.service === app?.service
@ -173,8 +173,12 @@ export const AppInfo = ({ app, myName }) => {
> >
<AppDownloadButtonText> <AppDownloadButtonText>
{isSelectedAppPinned {isSelectedAppPinned
? 'Unpin from dashboard' ? t('core:action.unpin_from_dashboard', {
: 'Pin to dashboard'} postProcess: 'capitalizeFirst',
})
: t('core:action.pin_from_dashboard', {
postProcess: 'capitalizeFirst',
})}
</AppDownloadButtonText> </AppDownloadButtonText>
</AppDownloadButton> </AppDownloadButton>
@ -194,7 +198,13 @@ export const AppInfo = ({ app, myName }) => {
}} }}
> >
<AppDownloadButtonText> <AppDownloadButtonText>
{isInstalled ? 'Open' : 'Download'} {isInstalled
? t('core:action.open', {
postProcess: 'capitalizeFirst',
})
: t('core:action.download', {
postProcess: 'capitalizeFirst',
})}
</AppDownloadButtonText> </AppDownloadButtonText>
</AppDownloadButton> </AppDownloadButton>
</Box> </Box>
@ -217,25 +227,40 @@ export const AppInfo = ({ app, myName }) => {
<Spacer width="16px" /> <Spacer width="16px" />
<AppsCategoryInfoSub> <AppsCategoryInfoSub>
<AppsCategoryInfoLabel>Category:</AppsCategoryInfoLabel> <AppsCategoryInfoLabel>
{t('core:category', {
postProcess: 'capitalizeFirst',
})}
:
</AppsCategoryInfoLabel>
<Spacer height="4px" /> <Spacer height="4px" />
<AppsCategoryInfoValue> <AppsCategoryInfoValue>
{app?.metadata?.categoryName || 'none'} {app?.metadata?.categoryName ||
t('core:none', {
postProcess: 'capitalizeFirst',
})}
</AppsCategoryInfoValue> </AppsCategoryInfoValue>
</AppsCategoryInfoSub> </AppsCategoryInfoSub>
</AppsCategoryInfo> </AppsCategoryInfo>
<Spacer height="30px" /> <Spacer height="30px" />
<AppInfoAppName>About this Q-App</AppInfoAppName> <AppInfoAppName>
{t('core:q_apps.about', {
postProcess: 'capitalizeFirst',
})}
</AppInfoAppName>
</AppsWidthLimiter> </AppsWidthLimiter>
<Spacer height="20px" /> <Spacer height="20px" />
<AppsInfoDescription> <AppsInfoDescription>
{app?.metadata?.description || 'No description'} {app?.metadata?.description ||
t('core:message.generic.no_description', {
postProcess: 'capitalizeFirst',
})}
</AppsInfoDescription> </AppsInfoDescription>
</Box> </Box>
</AppsLibraryContainer> </AppsLibraryContainer>

View File

@ -23,6 +23,7 @@ import {
} from '../../atoms/global'; } from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop'; import { saveToLocalStorage } from './AppsNavBarDesktop';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
export const AppInfoSnippet = ({ export const AppInfoSnippet = ({
app, app,
@ -41,6 +42,7 @@ export const AppInfoSnippet = ({
); );
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'auth', 'group']);
return ( return (
<AppInfoSnippetContainer <AppInfoSnippetContainer
@ -169,8 +171,13 @@ export const AppInfoSnippet = ({
}} }}
> >
<AppDownloadButtonText> <AppDownloadButtonText>
{' '} {isSelectedAppPinned
{isSelectedAppPinned ? 'Unpin' : 'Pin'} ? t('core:action.unpin', {
postProcess: 'capitalizeFirst',
})
: t('core:action.pin', {
postProcess: 'capitalizeFirst',
})}
</AppDownloadButtonText> </AppDownloadButtonText>
</AppDownloadButton> </AppDownloadButton>
@ -187,7 +194,13 @@ export const AppInfoSnippet = ({
}} }}
> >
<AppDownloadButtonText> <AppDownloadButtonText>
{isInstalled ? 'Open' : 'Download'} {isInstalled
? t('core:action.open', {
postProcess: 'capitalizeFirst',
})
: t('core:action.download', {
postProcess: 'capitalizeFirst',
})}
</AppDownloadButtonText> </AppDownloadButtonText>
</AppDownloadButton> </AppDownloadButton>
</AppInfoSnippetRight> </AppInfoSnippetRight>

View File

@ -1,20 +1,8 @@
import React, { useCallback, useContext, useEffect, useState } from 'react'; import React, { useCallback, useContext, useEffect, useState } from 'react';
import { import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppDownloadButton,
AppDownloadButtonText,
AppInfoAppName,
AppInfoSnippetContainer,
AppInfoSnippetLeft,
AppInfoSnippetMiddle,
AppInfoSnippetRight,
AppInfoUserName,
AppLibrarySubTitle, AppLibrarySubTitle,
AppPublishTagsContainer, AppPublishTagsContainer,
AppsLibraryContainer, AppsLibraryContainer,
AppsParent,
AppsWidthLimiter, AppsWidthLimiter,
PublishQAppCTAButton, PublishQAppCTAButton,
PublishQAppChoseFile, PublishQAppChoseFile,
@ -28,10 +16,7 @@ import {
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import { styled } from '@mui/system'; import { styled } from '@mui/system';
import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact } from '../../App'; import { MyContext, getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { useDropzone } from 'react-dropzone'; import { useDropzone } from 'react-dropzone';
@ -39,6 +24,7 @@ import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { fileToBase64 } from '../../utils/fileReading'; import { fileToBase64 } from '../../utils/fileReading';
import { useTranslation } from 'react-i18next';
const CustomSelect = styled(Select)({ const CustomSelect = styled(Select)({
border: '0.5px solid var(--50-white, #FFFFFF80)', border: '0.5px solid var(--50-white, #FFFFFF80)',
@ -82,6 +68,7 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
const [file, setFile] = useState(null); const [file, setFile] = useState(null);
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'auth', 'group']);
const [tag1, setTag1] = useState(''); const [tag1, setTag1] = useState('');
const [tag2, setTag2] = useState(''); const [tag2, setTag2] = useState('');
const [tag3, setTag3] = useState(''); const [tag3, setTag3] = useState('');
@ -107,9 +94,11 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
errors.forEach((error) => { errors.forEach((error) => {
if (error.code === 'file-too-large') { if (error.code === 'file-too-large') {
console.error( console.error(
`File ${file.name} is too large. Max size allowed is ${ t('core:message.error.file_too_large', {
maxFileSize / (1024 * 1024) filename: file.name,
} MB.` size: maxFileSize / (1024 * 1024),
postProcess: 'capitalizeFirst',
})
); );
} }
}); });
@ -143,6 +132,7 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
setTag5(myApp?.metadata?.tags[4] || ''); setTag5(myApp?.metadata?.tags[4] || '');
} }
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoading(''); setIsLoading('');
} }
@ -199,16 +189,25 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
}); });
if (missingFields.length > 0) { if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', '); const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`; const errorMsg = t('core:message.error.missing_fields', {
fields: missingFieldsString,
postProcess: 'capitalizeFirst',
});
throw new Error(errorMsg); throw new Error(errorMsg);
} }
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
message: 'Would you like to publish this app?', message: t('core:message.question.publish_app', {
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoading('Publishing... Please wait.'); setIsLoading(
t('core:message.generic.publishing', {
postProcess: 'capitalizeFirst',
})
);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage('publishOnQDN', { .sendMessage('publishOnQDN', {
@ -233,13 +232,19 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('core:message.success.published', {
'Successfully published. Please wait a couple minutes for the network to propogate the changes.', postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
const dataObj = { const dataObj = {
@ -258,7 +263,11 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
} catch (error) { } catch (error) {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error?.message || 'Unable to publish app', message:
error?.message ||
t('core:message.error.publish_app', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
} finally { } finally {
@ -279,18 +288,27 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
width: 'auto', width: 'auto',
}} }}
> >
<AppLibrarySubTitle>Create Apps!</AppLibrarySubTitle> <AppLibrarySubTitle>
{t('core:action.create_apps', {
postProcess: 'capitalizeFirst',
})}
!
</AppLibrarySubTitle>
<Spacer height="18px" /> <Spacer height="18px" />
<PublishQAppInfo> <PublishQAppInfo>
Note: Currently, only one App and Website is allowed per Name. {t('core:message.generic.one_app_per_name', {
postProcess: 'capitalizeFirst',
})}
</PublishQAppInfo> </PublishQAppInfo>
<Spacer height="18px" /> <Spacer height="18px" />
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}> <InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Name/App {t('core:name_app', {
postProcess: 'capitalizeFirst',
})}
</InputLabel> </InputLabel>
<CustomSelect <CustomSelect
@ -305,7 +323,9 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}} }}
> >
Select Name/App {t('core:action.select_name_app', {
postProcess: 'capitalizeFirst',
})}
</em> </em>
{/* This is the placeholder item */} {/* This is the placeholder item */}
</CustomMenuItem> </CustomMenuItem>
@ -317,7 +337,9 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
<Spacer height="15px" /> <Spacer height="15px" />
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}> <InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
App service type {t('core:app_service_type', {
postProcess: 'capitalizeFirst',
})}
</InputLabel> </InputLabel>
<CustomSelect <CustomSelect
@ -332,17 +354,29 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}} }}
> >
Select App Type {t('core:action.select_app_type', {
postProcess: 'capitalizeFirst',
})}
</em> </em>
</CustomMenuItem> </CustomMenuItem>
<CustomMenuItem value={'APP'}>App</CustomMenuItem> <CustomMenuItem value={'APP'}>
<CustomMenuItem value={'WEBSITE'}>Website</CustomMenuItem> {t('core:app', {
postProcess: 'capitalizeFirst',
})}
</CustomMenuItem>
<CustomMenuItem value={'WEBSITE'}>
{t('core:website', {
postProcess: 'capitalizeFirst',
})}
</CustomMenuItem>
</CustomSelect> </CustomSelect>
<Spacer height="15px" /> <Spacer height="15px" />
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}> <InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Title {t('core:title', {
postProcess: 'capitalizeFirst',
})}
</InputLabel> </InputLabel>
<InputBase <InputBase
@ -367,7 +401,9 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
<Spacer height="15px" /> <Spacer height="15px" />
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}> <InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Description {t('core:description', {
postProcess: 'capitalizeFirst',
})}
</InputLabel> </InputLabel>
<InputBase <InputBase
@ -392,7 +428,9 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
<Spacer height="15px" /> <Spacer height="15px" />
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}> <InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Category {t('core:category', {
postProcess: 'capitalizeFirst',
})}
</InputLabel> </InputLabel>
<CustomSelect <CustomSelect
@ -407,7 +445,9 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}} }}
> >
Select Category {t('core:action.select_category', {
postProcess: 'capitalizeFirst',
})}
</em> </em>
</CustomMenuItem> </CustomMenuItem>
{categories?.map((category) => { {categories?.map((category) => {
@ -422,7 +462,9 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
<Spacer height="15px" /> <Spacer height="15px" />
<InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}> <InputLabel sx={{ fontSize: '14px', marginBottom: '2px' }}>
Tags {t('core:tags', {
postProcess: 'capitalizeFirst',
})}
</InputLabel> </InputLabel>
<AppPublishTagsContainer> <AppPublishTagsContainer>
@ -516,7 +558,9 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
<Spacer height="30px" /> <Spacer height="30px" />
<PublishQAppInfo> <PublishQAppInfo>
Select .zip file containing static content:{' '} {t('core:message.generic.select_zip', {
postProcess: 'capitalizeFirst',
})}
</PublishQAppInfo> </PublishQAppInfo>
<Spacer height="10px" /> <Spacer height="10px" />
@ -536,7 +580,7 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
<PublishQAppChoseFile {...getRootProps()}> <PublishQAppChoseFile {...getRootProps()}>
{' '} {' '}
<input {...getInputProps()} /> <input {...getInputProps()} />
Choose File {t('core:action.choose_file', { postProcess: 'capitalizeFirst' })}
</PublishQAppChoseFile> </PublishQAppChoseFile>
<Spacer height="35px" /> <Spacer height="35px" />
@ -547,7 +591,7 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
}} }}
onClick={publishApp} onClick={publishApp}
> >
Publish {t('core:action.publish', { postProcess: 'capitalizeFirst' })}
</PublishQAppCTAButton> </PublishQAppCTAButton>
</AppsWidthLimiter> </AppsWidthLimiter>

View File

@ -7,6 +7,7 @@ import { StarFilledIcon } from '../../assets/Icons/StarFilled';
import { StarEmptyIcon } from '../../assets/Icons/StarEmpty'; import { StarEmptyIcon } from '../../assets/Icons/StarEmpty';
import { AppInfoUserName } from './Apps-styles'; import { AppInfoUserName } from './Apps-styles';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { useTranslation } from 'react-i18next';
export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => { export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
@ -19,6 +20,7 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null); const [infoSnack, setInfoSnack] = useState(null);
const hasCalledRef = useRef(false); const hasCalledRef = useRef(false);
const { t } = useTranslation(['core', 'group']);
const getRating = useCallback(async (name, service) => { const getRating = useCallback(async (name, service) => {
try { try {
@ -101,25 +103,39 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
const rateFunc = async (event, chosenValue, currentValue) => { const rateFunc = async (event, chosenValue, currentValue) => {
try { try {
const newValue = chosenValue || currentValue; const newValue = chosenValue || currentValue;
if (!myName) throw new Error('You need a name to rate.'); if (!myName)
throw new Error(
t('core:message.generic.name_rate', {
postProcess: 'capitalizeFirst',
})
);
if (!app?.name) return; if (!app?.name) return;
const fee = await getFee('CREATE_POLL'); const fee = await getFee('CREATE_POLL');
await show({ await show({
message: `Would you like to rate this app a rating of ${newValue}?. It will create a POLL tx.`, message: t('core:message.question.rate_app', {
rate: newValue,
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
if (hasPublishedRating === false) { if (hasPublishedRating === false) {
const pollName = `app-library-${app.service}-rating-${app.name}`; const pollName = `app-library-${app.service}-rating-${app.name}`;
const pollOptions = [`1, 2, 3, 4, 5, initialValue-${newValue}`]; const pollOptions = [`1, 2, 3, 4, 5, initialValue-${newValue}`];
const pollDescription = t('core:message.error.generic', {
name: app.name,
service: app.service,
postProcess: 'capitalizeFirst',
});
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage( .sendMessage(
'createPoll', 'createPoll',
{ {
pollName: pollName, pollName: pollName,
pollDescription: `Rating for ${app.service} ${app.name}`, pollDescription: pollDescription,
pollOptions: pollOptions, pollOptions: pollOptions,
pollOwnerAddress: myName, pollOwnerAddress: myName,
}, },
@ -133,8 +149,9 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
res(response); res(response);
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('core:message.success.rated_app', {
'Successfully rated. Please wait a couple minutes for the network to propogate the changes.', postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
} }
@ -150,7 +167,11 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
(option) => +option.optionName === +newValue (option) => +option.optionName === +newValue
); );
if (isNaN(optionIndex) || optionIndex === -1) if (isNaN(optionIndex) || optionIndex === -1)
throw new Error('Cannot find rating option'); throw new Error(
t('core:message.error.rating_option', {
postProcess: 'capitalizeFirst',
})
);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage( .sendMessage(
@ -169,8 +190,9 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
res(response); res(response);
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('core:message.success.rated_app', {
'Successfully rated. Please wait a couple minutes for the network to propogate the changes.', postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
} }
@ -184,7 +206,11 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
console.log('error', error); console.log('error', error);
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error?.message || 'Unable to rate', message:
error?.message ||
t('core:message.error.unable_rate', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
} }
@ -194,8 +220,8 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
<div> <div>
<Box <Box
sx={{ sx={{
display: 'flex',
alignItems: 'center', alignItems: 'center',
display: 'flex',
flexDirection: ratingCountPosition === 'top' ? 'column' : 'row', flexDirection: ratingCountPosition === 'top' ? 'column' : 'row',
}} }}
> >
@ -206,8 +232,11 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => {
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}{' '} (votesInfo?.voteCounts?.length === 6 ? 1 : 0)}{' '}
{' RATINGS'} {' RATINGS'}
</AppInfoUserName> </AppInfoUserName>
<Spacer height="6px" /> <Spacer height="6px" />
<AppInfoUserName>{value?.toFixed(1)}</AppInfoUserName> <AppInfoUserName>{value?.toFixed(1)}</AppInfoUserName>
<Spacer height="6px" /> <Spacer height="6px" />
</> </>
)} )}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react'; import { forwardRef, useEffect, useMemo, useState } from 'react';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
@ -7,7 +7,7 @@ import { useQortalMessageListener } from './useQortalMessageListener';
import { useThemeContext } from '../Theme/ThemeContext'; import { useThemeContext } from '../Theme/ThemeContext';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const AppViewer = React.forwardRef( export const AppViewer = forwardRef(
({ app, hide, isDevMode, skipAuth }, iframeRef) => { ({ app, hide, isDevMode, skipAuth }, iframeRef) => {
// const iframeRef = useRef(null); // const iframeRef = useRef(null);
const { window: frameWindow } = useFrame(); const { window: frameWindow } = useFrame();
@ -23,7 +23,7 @@ export const AppViewer = React.forwardRef(
); );
const [url, setUrl] = useState(''); const [url, setUrl] = useState('');
const { themeMode } = useThemeContext(); const { themeMode } = useThemeContext();
const { i18n } = useTranslation(['core']); const { i18n, t } = useTranslation(['core']);
const currentLang = i18n.language; const currentLang = i18n.language;
useEffect(() => { useEffect(() => {
@ -184,7 +184,13 @@ export const AppViewer = React.forwardRef(
// Timeout after 200ms if no response // Timeout after 200ms if no response
setTimeout(() => { setTimeout(() => {
window.removeEventListener('message', handleNavigationSuccess); window.removeEventListener('message', handleNavigationSuccess);
reject(new Error('Navigation timeout')); reject(
new Error(
t('core:message.error.navigation_timeout', {
postProcess: 'capitalizeFirst',
})
)
);
}, 200); }, 200);
const targetOrigin = iframeRef.current const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin ? new URL(iframeRef.current.src).origin

View File

@ -1,9 +1,8 @@
import React, { useContext } from 'react'; import { forwardRef } from 'react';
import { AppViewer } from './AppViewer'; import { AppViewer } from './AppViewer';
import Frame from 'react-frame-component'; import Frame from 'react-frame-component';
import { MyContext } from '../../App';
const AppViewerContainer = React.forwardRef( const AppViewerContainer = forwardRef(
({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => { ({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => {
return ( return (
<Frame <Frame

View File

@ -351,7 +351,7 @@ export const PublishQAppChoseFile = styled(ButtonBase)(({ theme }) => ({
justifyContent: 'center', justifyContent: 'center',
width: '120px', width: '120px',
'&:hover': { '&:hover': {
backgroundColor: 'action.hover', // background on hover backgroundColor: 'action.hover',
}, },
})); }));

View File

@ -16,20 +16,6 @@ import { Spacer } from '../../common/Spacer';
import { AppInfoSnippet } from './AppInfoSnippet'; import { AppInfoSnippet } from './AppInfoSnippet';
import { Virtuoso } from 'react-virtuoso'; import { Virtuoso } from 'react-virtuoso';
const ScrollerStyled = styled('div')({
// Hide scrollbar for WebKit browsers (Chrome, Safari)
'::-webkit-scrollbar': {
width: '0px',
height: '0px',
},
// Hide scrollbar for Firefox
scrollbarWidth: 'none',
// Hide scrollbar for IE and older Edge
msOverflowStyle: 'none',
});
const StyledVirtuosoContainer = styled('div')({ const StyledVirtuosoContainer = styled('div')({
position: 'relative', position: 'relative',
width: '100%', width: '100%',
@ -99,7 +85,8 @@ export const AppsCategoryDesktop = ({
}, [debouncedValue, categoryList]); }, [debouncedValue, categoryList]);
const rowRenderer = (index) => { const rowRenderer = (index) => {
let app = searchedList[index]; const app = searchedList[index];
return ( return (
<AppInfoSnippet <AppInfoSnippet
key={`${app?.service}-${app?.name}`} key={`${app?.service}-${app?.name}`}
@ -205,9 +192,6 @@ export const AppsCategoryDesktop = ({
itemContent={rowRenderer} itemContent={rowRenderer}
atBottomThreshold={50} atBottomThreshold={50}
followOutput="smooth" followOutput="smooth"
// components={{
// Scroller: ScrollerStyled, // Use the styled scroller component
// }}
/> />
</StyledVirtuosoContainer> </StyledVirtuosoContainer>
</AppsWidthLimiter> </AppsWidthLimiter>

View File

@ -17,7 +17,6 @@ import { AppsCategoryDesktop } from './AppsCategoryDesktop';
import { AppsNavBarDesktop } from './AppsNavBarDesktop'; import { AppsNavBarDesktop } from './AppsNavBarDesktop';
import { Box, ButtonBase, useTheme } from '@mui/material'; import { Box, ButtonBase, useTheme } from '@mui/material';
import { HomeIcon } from '../../assets/Icons/HomeIcon'; import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import { Save } from '../Save/Save'; import { Save } from '../Save/Save';
import { IconWrapper } from '../Desktop/DesktopFooter'; import { IconWrapper } from '../Desktop/DesktopFooter';
import { enabledDevModeAtom } from '../../atoms/global'; import { enabledDevModeAtom } from '../../atoms/global';
@ -25,6 +24,7 @@ import { AppsIcon } from '../../assets/Icons/AppsIcon';
import { CoreSyncStatus } from '../CoreSyncStatus'; import { CoreSyncStatus } from '../CoreSyncStatus';
import { MessagingIconFilled } from '../../assets/Icons/MessagingIconFilled'; import { MessagingIconFilled } from '../../assets/Icons/MessagingIconFilled';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 8 }); const uid = new ShortUniqueId({ length: 8 });
@ -49,19 +49,29 @@ export const AppsDesktop = ({
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
const iframeRefs = useRef({}); const iframeRefs = useRef({});
const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom);
const { showTutorial } = useContext(MyContext); const { showTutorial } = useContext(MyContext);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const myApp = useMemo(() => { const myApp = useMemo(() => {
return availableQapps.find( return availableQapps.find(
(app) => app.name === myName && app.service === 'APP' (app) =>
app.name === myName &&
app.service ===
t('core:app', {
postProcess: 'capitalizeAll',
})
); );
}, [myName, availableQapps]); }, [myName, availableQapps]);
const myWebsite = useMemo(() => { const myWebsite = useMemo(() => {
return availableQapps.find( return availableQapps.find(
(app) => app.name === myName && app.service === 'WEBSITE' (app) =>
app.name === myName &&
app.service ===
t('core:website', {
postProcess: 'capitalizeAll',
})
); );
}, [myName, availableQapps]); }, [myName, availableQapps]);
@ -99,8 +109,6 @@ export const AppsDesktop = ({
setCategories(responseData); setCategories(responseData);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally {
// dispatch(setIsLoadingGlobal(false))
} }
}, []); }, []);
@ -108,7 +116,6 @@ export const AppsDesktop = ({
try { try {
let apps = []; let apps = [];
let websites = []; let websites = [];
// dispatch(setIsLoadingGlobal(true))
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&limit=0&includestatus=true&includemetadata=true`; const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
const response = await fetch(url, { const response = await fetch(url, {
@ -117,6 +124,7 @@ export const AppsDesktop = ({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); });
if (!response?.ok) return; if (!response?.ok) return;
const responseData = await response.json(); const responseData = await response.json();
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&limit=0&includestatus=true&includemetadata=true`; const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
@ -127,6 +135,7 @@ export const AppsDesktop = ({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); });
if (!responseWebsites?.ok) return; if (!responseWebsites?.ok) return;
const responseDataWebsites = await responseWebsites.json(); const responseDataWebsites = await responseWebsites.json();
@ -136,8 +145,6 @@ export const AppsDesktop = ({
setAvailableQapps(combine); setAvailableQapps(combine);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally {
// dispatch(setIsLoadingGlobal(false))
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -251,7 +258,6 @@ export const AppsDesktop = ({
setTabs((prev) => [...prev, newTab]); setTabs((prev) => [...prev, newTab]);
setSelectedTab(newTab); setSelectedTab(newTab);
setMode('viewer'); setMode('viewer');
setIsNewTabWindow(false); setIsNewTabWindow(false);
}; };
@ -262,6 +268,7 @@ export const AppsDesktop = ({
unsubscribeFromEvent('addTab', addTabFunc); unsubscribeFromEvent('addTab', addTabFunc);
}; };
}, [tabs]); }, [tabs]);
const setSelectedTabFunc = (e) => { const setSelectedTabFunc = (e) => {
const data = e.detail?.data; const data = e.detail?.data;
if (e.detail?.isDevMode) return; if (e.detail?.isDevMode) return;
@ -331,21 +338,21 @@ export const AppsDesktop = ({
return ( return (
<AppsParent <AppsParent
sx={{ sx={{
position: !show && 'fixed',
left: !show && '-200vw',
flexDirection: 'row', flexDirection: 'row',
left: !show && '-200vw',
position: !show && 'fixed',
}} }}
> >
<Box <Box
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
backgroundColor: theme.palette.background.surface,
borderRight: `1px solid ${theme.palette.border.subtle}`,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '25px', gap: '25px',
height: '100vh', height: '100vh',
width: '60px', width: '60px',
backgroundColor: theme.palette.background.surface,
borderRight: `1px solid ${theme.palette.border.subtle}`,
}} }}
> >
<ButtonBase <ButtonBase
@ -408,6 +415,7 @@ export const AppsDesktop = ({
/> />
</IconWrapper> </IconWrapper>
</ButtonBase> </ButtonBase>
<Save isDesktop disableWidth myName={myName} /> <Save isDesktop disableWidth myName={myName} />
{isEnabledDevMode && ( {isEnabledDevMode && (
<ButtonBase <ButtonBase
@ -446,13 +454,14 @@ export const AppsDesktop = ({
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
width: '100%',
flexDirection: 'column', flexDirection: 'column',
height: '100vh', height: '100vh',
overflow: 'auto', overflow: 'auto',
width: '100%',
}} }}
> >
<Spacer height="30px" /> <Spacer height="30px" />
<AppsHomeDesktop <AppsHomeDesktop
myName={myName} myName={myName}
availableQapps={availableQapps} availableQapps={availableQapps}
@ -480,12 +489,14 @@ export const AppsDesktop = ({
{mode === 'appInfo-from-category' && !selectedTab && ( {mode === 'appInfo-from-category' && !selectedTab && (
<AppInfo app={selectedAppInfo} myName={myName} /> <AppInfo app={selectedAppInfo} myName={myName} />
)} )}
<AppsCategoryDesktop <AppsCategoryDesktop
availableQapps={availableQapps} availableQapps={availableQapps}
isShow={mode === 'category' && !selectedTab} isShow={mode === 'category' && !selectedTab}
category={selectedCategory} category={selectedCategory}
myName={myName} myName={myName}
/> />
{mode === 'publish' && !selectedTab && ( {mode === 'publish' && !selectedTab && (
<AppPublish <AppPublish
names={myName ? [myName] : []} names={myName ? [myName] : []}
@ -493,6 +504,7 @@ export const AppsDesktop = ({
myAddress={myAddress} myAddress={myAddress}
/> />
)} )}
{tabs.map((tab) => { {tabs.map((tab) => {
if (!iframeRefs.current[tab.tabId]) { if (!iframeRefs.current[tab.tabId]) {
iframeRefs.current[tab.tabId] = React.createRef(); iframeRefs.current[tab.tabId] = React.createRef();
@ -521,6 +533,7 @@ export const AppsDesktop = ({
}} }}
> >
<Spacer height="30px" /> <Spacer height="30px" />
<AppsHomeDesktop <AppsHomeDesktop
myName={myName} myName={myName}
availableQapps={availableQapps} availableQapps={availableQapps}

View File

@ -1,7 +1,6 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { AppsDevModeHome } from './AppsDevModeHome'; import { AppsDevModeHome } from './AppsDevModeHome';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { import {
executeEvent, executeEvent,
subscribeToEvent, subscribeToEvent,
@ -10,7 +9,6 @@ import {
import { AppsParent } from './Apps-styles'; import { AppsParent } from './Apps-styles';
import AppViewerContainer from './AppViewerContainer'; import AppViewerContainer from './AppViewerContainer';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
import { Box, ButtonBase, useTheme } from '@mui/material'; import { Box, ButtonBase, useTheme } from '@mui/material';
import { HomeIcon } from '../../assets/Icons/HomeIcon'; import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { Save } from '../Save/Save'; import { Save } from '../Save/Save';
@ -137,7 +135,6 @@ export const AppsDevMode = ({
setTabs(copyTabs); setTabs(copyTabs);
setSelectedTab(newTab); setSelectedTab(newTab);
setMode('viewer'); setMode('viewer');
setIsNewTabWindow(false); setIsNewTabWindow(false);
}; };
@ -260,6 +257,7 @@ export const AppsDevMode = ({
} }
/> />
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setDesktopViewMode('apps'); setDesktopViewMode('apps');
@ -282,6 +280,7 @@ export const AppsDevMode = ({
/> />
</IconWrapper> </IconWrapper>
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setDesktopViewMode('chat'); setDesktopViewMode('chat');
@ -310,7 +309,9 @@ export const AppsDevMode = ({
/> />
</IconWrapper> </IconWrapper>
</ButtonBase> </ButtonBase>
<Save isDesktop disableWidth myName={myName} /> <Save isDesktop disableWidth myName={myName} />
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setDesktopViewMode('dev'); setDesktopViewMode('dev');
@ -342,13 +343,14 @@ export const AppsDevMode = ({
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
width: '100%',
flexDirection: 'column', flexDirection: 'column',
height: '100vh', height: '100vh',
overflow: 'auto', overflow: 'auto',
width: '100%',
}} }}
> >
<Spacer height="30px" /> <Spacer height="30px" />
<AppsDevModeHome <AppsDevModeHome
myName={myName} myName={myName}
availableQapps={availableQapps} availableQapps={availableQapps}
@ -387,6 +389,7 @@ export const AppsDevMode = ({
}} }}
> >
<Spacer height="30px" /> <Spacer height="30px" />
<AppsDevModeHome <AppsDevModeHome
myName={myName} myName={myName}
availableQapps={availableQapps} availableQapps={availableQapps}

View File

@ -1,14 +1,12 @@
import React, { useContext, useMemo, useState } from 'react'; import { useContext, useState } from 'react';
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
AppCircleLabel, AppCircleLabel,
AppLibrarySubTitle, AppLibrarySubTitle,
AppsContainer, AppsContainer,
AppsParent,
} from './Apps-styles'; } from './Apps-styles';
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import { import {
Avatar, Avatar,
Box, Box,
@ -17,13 +15,11 @@ import {
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogContentText,
DialogTitle, DialogTitle,
Input, Input,
} from '@mui/material'; } from '@mui/material';
import { Add } from '@mui/icons-material'; import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact } from '../../App'; import { MyContext, getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { useModal } from '../../common/useModal'; import { useModal } from '../../common/useModal';
@ -31,6 +27,8 @@ import { createEndpoint, isUsingLocal } from '../../background';
import { Label } from '../Group/AddGroup'; import { Label } from '../Group/AddGroup';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
import swaggerSVG from '../../assets/svgs/swagger.svg'; import swaggerSVG from '../../assets/svgs/swagger.svg';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 8 }); const uid = new ShortUniqueId({ length: 8 });
export const AppsDevModeHome = ({ export const AppsDevModeHome = ({
@ -43,7 +41,7 @@ export const AppsDevModeHome = ({
const [domain, setDomain] = useState('127.0.0.1'); const [domain, setDomain] = useState('127.0.0.1');
const [port, setPort] = useState(''); const [port, setPort] = useState('');
const [selectedPreviewFile, setSelectedPreviewFile] = useState(null); const [selectedPreviewFile, setSelectedPreviewFile] = useState(null);
const { t } = useTranslation(['core', 'group']);
const { isShow, onCancel, onOk, show, message } = useModal(); const { isShow, onCancel, onOk, show, message } = useModal();
const { const {
openSnackGlobal, openSnackGlobal,
@ -61,6 +59,7 @@ export const AppsDevModeHome = ({
console.log('No file selected.'); console.log('No file selected.');
} }
}; };
const handleSelectDirectry = async (existingDirectoryPath) => { const handleSelectDirectry = async (existingDirectoryPath) => {
const { buffer, directoryPath } = const { buffer, directoryPath } =
await window.electron.selectAndZipDirectory(existingDirectoryPath); await window.electron.selectAndZipDirectory(existingDirectoryPath);
@ -79,8 +78,7 @@ export const AppsDevModeHome = ({
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: message: '',
'Please use your local node for dev mode! Logout and use Local node.',
}); });
return; return;
} }
@ -115,20 +113,21 @@ export const AppsDevModeHome = ({
const usingLocal = await isUsingLocal(); const usingLocal = await isUsingLocal();
if (!usingLocal) { if (!usingLocal) {
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: message: t('core:message.generic.devmode_local_node', {
'Please use your local node for dev mode! Logout and use Local node.', postProcess: 'capitalizeFirst',
}),
}); });
return; return;
} }
if (!myName) { if (!myName) {
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: 'You need a name to use preview', message: t('core:message.generic.name_preview', {
postProcess: 'capitalizeFirst',
}),
}); });
return; return;
} }
@ -137,15 +136,16 @@ export const AppsDevModeHome = ({
if (!buffer) { if (!buffer) {
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: 'Please select a file', message: t('core:message.generic.select_file', {
postProcess: 'capitalizeFirst',
}),
}); });
return; return;
} }
const postBody = Buffer.from(buffer).toString('base64');
const postBody = Buffer.from(buffer).toString('base64');
const endpoint = await createEndpoint( const endpoint = await createEndpoint(
`/arbitrary/APP/${myName}/zip?preview=true` `/arbitrary/APP/${myName}/zip?preview=true`
); );
@ -156,6 +156,7 @@ export const AppsDevModeHome = ({
}, },
body: postBody, body: postBody,
}); });
if (!response?.ok) throw new Error('Invalid zip'); if (!response?.ok) throw new Error('Invalid zip');
const previewPath = await response.text(); const previewPath = await response.text();
if (tabId) { if (tabId) {
@ -192,20 +193,21 @@ export const AppsDevModeHome = ({
const usingLocal = await isUsingLocal(); const usingLocal = await isUsingLocal();
if (!usingLocal) { if (!usingLocal) {
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: message: t('core:message.generic.devmode_local_node', {
'Please use your local node for dev mode! Logout and use Local node.', postProcess: 'capitalizeFirst',
}),
}); });
return; return;
} }
if (!myName) { if (!myName) {
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: 'You need a name to use preview', message: t('core:message.generic.name_preview', {
postProcess: 'capitalizeFirst',
}),
}); });
return; return;
} }
@ -214,15 +216,16 @@ export const AppsDevModeHome = ({
if (!buffer) { if (!buffer) {
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: 'Please select a file', message: t('core:message.generic.select_file', {
postProcess: 'capitalizeFirst',
}),
}); });
return; return;
} }
const postBody = Buffer.from(buffer).toString('base64');
const postBody = Buffer.from(buffer).toString('base64');
const endpoint = await createEndpoint( const endpoint = await createEndpoint(
`/arbitrary/APP/${myName}/zip?preview=true` `/arbitrary/APP/${myName}/zip?preview=true`
); );
@ -233,8 +236,15 @@ export const AppsDevModeHome = ({
}, },
body: postBody, body: postBody,
}); });
if (!response?.ok) throw new Error('Invalid zip');
if (!response?.ok)
throw new Error(
t('core:message.error.invalid_zip', {
postProcess: 'capitalizeFirst',
})
);
const previewPath = await response.text(); const previewPath = await response.text();
if (tabId) { if (tabId) {
executeEvent('appsDevModeUpdateTab', { executeEvent('appsDevModeUpdateTab', {
data: { data: {
@ -276,7 +286,7 @@ export const AppsDevModeHome = ({
fontSize: '30px', fontSize: '30px',
}} }}
> >
Dev Mode Apps {t('core:devmode_apps', { postProcess: 'capitalizeFirst' })}
</AppLibrarySubTitle> </AppLibrarySubTitle>
</AppsContainer> </AppsContainer>
@ -301,7 +311,9 @@ export const AppsDevModeHome = ({
<AppCircle> <AppCircle>
<Add>+</Add> <Add>+</Add>
</AppCircle> </AppCircle>
<AppCircleLabel>Server</AppCircleLabel> <AppCircleLabel>
{t('core:server', { postProcess: 'capitalizeFirst' })}
</AppCircleLabel>
</AppCircleContainer> </AppCircleContainer>
</ButtonBase> </ButtonBase>
@ -319,7 +331,9 @@ export const AppsDevModeHome = ({
<Add>+</Add> <Add>+</Add>
</AppCircle> </AppCircle>
<AppCircleLabel>Zip</AppCircleLabel> <AppCircleLabel>
{t('core:zip', { postProcess: 'capitalizeFirst' })}
</AppCircleLabel>
</AppCircleContainer> </AppCircleContainer>
</ButtonBase> </ButtonBase>
@ -336,7 +350,9 @@ export const AppsDevModeHome = ({
<AppCircle> <AppCircle>
<Add>+</Add> <Add>+</Add>
</AppCircle> </AppCircle>
<AppCircleLabel>Directory</AppCircleLabel> <AppCircleLabel>
{t('core:directory', { postProcess: 'capitalizeFirst' })}
</AppCircleLabel>
</AppCircleContainer> </AppCircleContainer>
</ButtonBase> </ButtonBase>
@ -365,7 +381,9 @@ export const AppsDevModeHome = ({
objectFit: 'fill', objectFit: 'fill',
}, },
}} }}
alt="Q-Sandbox" alt={t('core:q_apps.q_sandbox', {
postProcess: 'capitalizeFirst',
})}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/Q-Sandbox/qortal_avatar?async=true`} src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/Q-Sandbox/qortal_avatar?async=true`}
> >
<img <img
@ -378,7 +396,11 @@ export const AppsDevModeHome = ({
</Avatar> </Avatar>
</AppCircle> </AppCircle>
<AppCircleLabel>Q-Sandbox</AppCircleLabel> <AppCircleLabel>
{t('core:q_apps.q_sandbox', {
postProcess: 'capitalizeFirst',
})}
</AppCircleLabel>
</AppCircleContainer> </AppCircleContainer>
</ButtonBase> </ButtonBase>
@ -407,7 +429,9 @@ export const AppsDevModeHome = ({
objectFit: 'fill', objectFit: 'fill',
}, },
}} }}
alt="API" alt={t('core:api', {
postProcess: 'capitalizeAll',
})}
src={swaggerSVG} src={swaggerSVG}
> >
<img <img
@ -420,7 +444,11 @@ export const AppsDevModeHome = ({
</Avatar> </Avatar>
</AppCircle> </AppCircle>
<AppCircleLabel>API</AppCircleLabel> <AppCircleLabel>
{t('core:api', {
postProcess: 'capitalizeAll',
})}
</AppCircleLabel>
</AppCircleContainer> </AppCircleContainer>
</ButtonBase> </ButtonBase>
</AppsContainer> </AppsContainer>
@ -437,7 +465,9 @@ export const AppsDevModeHome = ({
}} }}
> >
<DialogTitle id="alert-dialog-title"> <DialogTitle id="alert-dialog-title">
{'Add custom framework'} {t('core:action.add_custom_framework', {
postProcess: 'capitalizeFirst',
})}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
@ -448,13 +478,20 @@ export const AppsDevModeHome = ({
gap: '5px', gap: '5px',
}} }}
> >
<Label>Domain</Label> <Label>
{t('core:domain', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Input <Input
placeholder="Domain" placeholder={t('core:domain', {
postProcess: 'capitalizeFirst',
})}
value={domain} value={domain}
onChange={(e) => setDomain(e.target.value)} onChange={(e) => setDomain(e.target.value)}
/> />
</Box> </Box>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -463,9 +500,15 @@ export const AppsDevModeHome = ({
marginTop: '15px', marginTop: '15px',
}} }}
> >
<Label>Port</Label> <Label>
{t('core:port', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Input <Input
placeholder="Port" placeholder={t('core:port', {
postProcess: 'capitalizeFirst',
})}
value={port} value={port}
onChange={(e) => setPort(e.target.value)} onChange={(e) => setPort(e.target.value)}
/> />
@ -474,15 +517,20 @@ export const AppsDevModeHome = ({
<DialogActions> <DialogActions>
<Button variant="contained" onClick={onCancel}> <Button variant="contained" onClick={onCancel}>
Close {t('core:action.close', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Button <Button
disabled={!domain || !port} disabled={!domain || !port}
variant="contained" variant="contained"
onClick={() => onOk({ portVal: port, domainVal: domain })} onClick={() => onOk({ portVal: port, domainVal: domain })}
autoFocus autoFocus
> >
Add {t('core:action.add', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -23,7 +23,6 @@ export const AppsDevModeNavBar = () => {
const [navigationController, setNavigationController] = useAtom( const [navigationController, setNavigationController] = useAtom(
navigationControllerAtom navigationControllerAtom
); );
const theme = useTheme(); const theme = useTheme();
const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const tabsRef = useRef(null); const tabsRef = useRef(null);
@ -80,13 +79,13 @@ export const AppsDevModeNavBar = () => {
return ( return (
<AppsNavBarParent <AppsNavBarParent
sx={{ sx={{
position: 'relative', borderRadius: '0px 30px 30px 0px',
flexDirection: 'column', flexDirection: 'column',
width: '59px',
height: 'unset', height: 'unset',
maxHeight: '70vh', maxHeight: '70vh',
borderRadius: '0px 30px 30px 0px',
padding: '10px', padding: '10px',
position: 'relative',
width: '59px',
}} }}
> >
<AppsNavBarLeft <AppsNavBarLeft

View File

@ -16,6 +16,7 @@ import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
import { AppsPrivate } from './AppsPrivate'; import { AppsPrivate } from './AppsPrivate';
import ThemeSelector from '../Theme/ThemeSelector'; import ThemeSelector from '../Theme/ThemeSelector';
import LanguageSelector from '../Language/LanguageSelector'; import LanguageSelector from '../Language/LanguageSelector';
import { useTranslation } from 'react-i18next';
export const AppsHomeDesktop = ({ export const AppsHomeDesktop = ({
setMode, setMode,
@ -27,6 +28,7 @@ export const AppsHomeDesktop = ({
}) => { }) => {
const [qortalUrl, setQortalUrl] = useState(''); const [qortalUrl, setQortalUrl] = useState('');
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const openQortalUrl = () => { const openQortalUrl = () => {
try { try {
@ -42,6 +44,7 @@ export const AppsHomeDesktop = ({
console.log(error); console.log(error);
} }
}; };
return ( return (
<> <>
<AppsContainer <AppsContainer
@ -54,7 +57,7 @@ export const AppsHomeDesktop = ({
fontSize: '30px', fontSize: '30px',
}} }}
> >
Apps Dashboard {t('core:apps_dashboard', { postProcess: 'capitalizeFirst' })}
</AppLibrarySubTitle> </AppLibrarySubTitle>
</AppsContainer> </AppsContainer>
@ -67,14 +70,14 @@ export const AppsHomeDesktop = ({
> >
<Box <Box
sx={{ sx={{
display: 'flex',
gap: '20px',
alignItems: 'center', alignItems: 'center',
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
padding: '7px',
borderRadius: '20px', borderRadius: '20px',
width: '100%', display: 'flex',
gap: '20px',
maxWidth: '500px', maxWidth: '500px',
padding: '7px',
width: '100%',
}} }}
> >
<Input <Input
@ -144,7 +147,9 @@ export const AppsHomeDesktop = ({
<AddIcon /> <AddIcon />
</AppCircle> </AppCircle>
<AppCircleLabel>Library</AppCircleLabel> <AppCircleLabel>
{t('core:library', { postProcess: 'capitalizeFirst' })}
</AppCircleLabel>
</AppCircleContainer> </AppCircleContainer>
</ButtonBase> </ButtonBase>

View File

@ -41,6 +41,7 @@ import { Virtuoso } from 'react-virtuoso';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { ComposeP, ShowMessageReturnButton } from '../Group/Forum/Mail-styles'; import { ComposeP, ShowMessageReturnButton } from '../Group/Forum/Mail-styles';
import { ReturnIcon } from '../../assets/Icons/ReturnIcon.tsx'; import { ReturnIcon } from '../../assets/Icons/ReturnIcon.tsx';
import { useTranslation } from 'react-i18next';
const officialAppList = [ const officialAppList = [
'q-tube', 'q-tube',
@ -104,6 +105,7 @@ export const AppsLibraryDesktop = ({
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef(null); const virtuosoRef = useRef(null);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const officialApps = useMemo(() => { const officialApps = useMemo(() => {
return availableQapps.filter( return availableQapps.filter(
@ -210,9 +212,13 @@ export const AppsLibraryDesktop = ({
ml: 1, ml: 1,
paddingLeft: '12px', paddingLeft: '12px',
}} }}
placeholder="Search for apps" placeholder={t('core:action.search_apps', {
postProcess: 'capitalizeFirst',
})}
inputProps={{ inputProps={{
'aria-label': 'Search for apps', 'aria-label': t('core:action.search_apps', {
postProcess: 'capitalizeFirst',
}),
fontSize: '16px', fontSize: '16px',
fontWeight: 400, fontWeight: 400,
}} }}
@ -276,7 +282,11 @@ export const AppsLibraryDesktop = ({
}} }}
> >
<ReturnIcon /> <ReturnIcon />
<ComposeP>Return to Apps Dashboard</ComposeP> <ComposeP>
{t('core:action.return_apps_dashboard', {
postProcess: 'capitalizeFirst',
})}
</ComposeP>
</ShowMessageReturnButton> </ShowMessageReturnButton>
<Spacer height="20px" /> <Spacer height="20px" />
@ -302,7 +312,11 @@ export const AppsLibraryDesktop = ({
</AppsWidthLimiter> </AppsWidthLimiter>
) : searchedList?.length === 0 && debouncedValue ? ( ) : searchedList?.length === 0 && debouncedValue ? (
<AppsWidthLimiter> <AppsWidthLimiter>
<Typography>No results</Typography> <Typography>
{t('core:message.generic.no_results', {
postProcess: 'capitalizeFirst',
})}
</Typography>
</AppsWidthLimiter> </AppsWidthLimiter>
) : ( ) : (
<> <>
@ -311,7 +325,7 @@ export const AppsLibraryDesktop = ({
fontSize: '30px', fontSize: '30px',
}} }}
> >
Official Apps {t('core:apps_official', { postProcess: 'capitalizeFirst' })}
</AppLibrarySubTitle> </AppLibrarySubTitle>
<Spacer height="45px" /> <Spacer height="45px" />
@ -396,7 +410,13 @@ export const AppsLibraryDesktop = ({
textAlign: 'start', textAlign: 'start',
}} }}
> >
{hasPublishApp ? 'Update your app' : 'Publish your app'} {hasPublishApp
? t('core:action.update_app', {
postProcess: 'capitalizeFirst',
})
: t('core:action.publish_app', {
postProcess: 'capitalizeFirst',
})}
</AppLibrarySubTitle> </AppLibrarySubTitle>
<Spacer height="18px" /> <Spacer height="18px" />
@ -422,7 +442,13 @@ export const AppsLibraryDesktop = ({
}} }}
> >
<PublishQAppCTAButton> <PublishQAppCTAButton>
{hasPublishApp ? 'Update' : 'Publish'} {hasPublishApp
? t('core:action.update', {
postProcess: 'capitalizeFirst',
})
: t('core:action.publish', {
postProcess: 'capitalizeFirst',
})}
</PublishQAppCTAButton> </PublishQAppCTAButton>
<Spacer width="20px" /> <Spacer width="20px" />
@ -441,7 +467,9 @@ export const AppsLibraryDesktop = ({
fontSize: '30px', fontSize: '30px',
}} }}
> >
Categories {t('core:category_other', {
postProcess: 'capitalizeFirst',
})}
</AppLibrarySubTitle> </AppLibrarySubTitle>
<Spacer height="18px" /> <Spacer height="18px" />
@ -480,7 +508,7 @@ export const AppsLibraryDesktop = ({
}, },
}} }}
> >
All {t('core:all', { postProcess: 'capitalizeFirst' })}
</Box> </Box>
</ButtonBase> </ButtonBase>

View File

@ -32,6 +32,7 @@ import {
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from '../../atoms/global'; } from '../../atoms/global';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
export function saveToLocalStorage(key, subKey, newValue) { export function saveToLocalStorage(key, subKey, newValue) {
try { try {
@ -75,7 +76,7 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
); );
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const tabsRef = useRef(null); const tabsRef = useRef(null);
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
@ -238,6 +239,7 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
}} }}
/> />
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
onClick={(e) => { onClick={(e) => {
if (!selectedTab) return; if (!selectedTab) return;
@ -274,9 +276,9 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
paper: { paper: {
sx: { sx: {
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
borderRadius: '5px',
color: theme.palette.text.primary, color: theme.palette.text.primary,
width: '148px', width: '148px',
borderRadius: '5px',
}, },
}, },
}} }}
@ -375,9 +377,18 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
: theme.palette.text.primary, : theme.palette.text.primary,
}, },
}} }}
primary={`${isSelectedAppPinned ? 'Unpin app' : 'Pin app'}`} primary={`${
isSelectedAppPinned
? t('core:action.unpin_app', {
postProcess: 'capitalizeFirst',
})
: t('core:action.pin_app', {
postProcess: 'capitalizeFirst',
})
}}`}
/> />
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
if (selectedTab?.refreshFunc) { if (selectedTab?.refreshFunc) {
@ -404,6 +415,7 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
}} }}
/> />
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
sx={{ sx={{
'& .MuiTypography-root': { '& .MuiTypography-root': {
@ -447,7 +459,9 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
color: theme.palette.text.primary, color: theme.palette.text.primary,
}, },
}} }}
primary="Copy link" primary={t('core:action.copy_link', {
postProcess: 'capitalizeFirst',
})}
/> />
</MenuItem> </MenuItem>
)} )}

View File

@ -42,6 +42,7 @@ import { fileToBase64 } from '../../utils/fileReading';
import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { objectToBase64 } from '../../qdn/encryption/group-encryption';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
const maxFileSize = 50 * 1024 * 1024; // 50MB const maxFileSize = 50 * 1024 * 1024; // 50MB
@ -71,6 +72,7 @@ export const AppsPrivate = ({ myName, myAddress }) => {
const [memberGroups] = useAtom(memberGroupsAtom); const [memberGroups] = useAtom(memberGroupsAtom);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const myGroupsPrivate = useMemo(() => { const myGroupsPrivate = useMemo(() => {
return memberGroups?.filter( return memberGroups?.filter(
@ -107,9 +109,11 @@ export const AppsPrivate = ({ myName, myAddress }) => {
errors.forEach((error) => { errors.forEach((error) => {
if (error.code === 'file-too-large') { if (error.code === 'file-too-large') {
console.error( console.error(
`File ${file.name} is too large. Max size allowed is ${ t('core:message.error.file_too_large', {
maxFileSize / (1024 * 1024) filename: file.name,
} MB.` size: maxFileSize / (1024 * 1024),
postProcess: 'capitalizeFirst',
})
); );
} }
}); });
@ -120,7 +124,6 @@ export const AppsPrivate = ({ myName, myAddress }) => {
const addPrivateApp = async () => { const addPrivateApp = async () => {
try { try {
if (privateAppValues?.groupId === 0) return; if (privateAppValues?.groupId === 0) return;
await openApp(privateAppValues, true); await openApp(privateAppValues, true);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -148,9 +151,28 @@ export const AppsPrivate = ({ myName, myAddress }) => {
const publishPrivateApp = async () => { const publishPrivateApp = async () => {
try { try {
if (selectedGroup === 0) return; if (selectedGroup === 0) return;
if (!logo) throw new Error('Please select an image for a logo');
if (!name) throw new Error('Please select a Qortal name'); if (!logo)
if (!newPrivateAppValues?.name) throw new Error('Your app needs a name'); throw new Error(
t('core:message.generic.select_image', {
postProcess: 'capitalizeFirst',
})
);
if (!myName)
throw new Error(
t('core:message.generic.name_publish', {
postProcess: 'capitalizeFirst',
})
);
if (!newPrivateAppValues?.name)
throw new Error(
t('core:message.error.app_need_name', {
postProcess: 'capitalizeFirst',
})
);
const base64Logo = await fileToBase64(logo); const base64Logo = await fileToBase64(logo);
const base64App = await fileToBase64(file); const base64App = await fileToBase64(file);
const objectToSave = { const objectToSave = {
@ -161,7 +183,6 @@ export const AppsPrivate = ({ myName, myAddress }) => {
const object64 = await objectToBase64(objectToSave); const object64 = await objectToBase64(objectToSave);
const decryptedData = await window.sendMessage( const decryptedData = await window.sendMessage(
'ENCRYPT_QORTAL_GROUP_DATA', 'ENCRYPT_QORTAL_GROUP_DATA',
{ {
base64: object64, base64: object64,
groupId: selectedGroup, groupId: selectedGroup,
@ -170,16 +191,22 @@ export const AppsPrivate = ({ myName, myAddress }) => {
if (decryptedData?.error) { if (decryptedData?.error) {
throw new Error( throw new Error(
decryptedData?.error || 'Unable to encrypt app. App not published' decryptedData?.error ||
t('core:message.error.unable_encrypt_app', {
postProcess: 'capitalizeFirst',
})
); );
} }
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
message: 'Would you like to publish this app?', message: t('core:message.question.publish_app', {
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage('publishOnQDN', { .sendMessage('publishOnQDN', {
@ -197,7 +224,12 @@ export const AppsPrivate = ({ myName, myAddress }) => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
@ -215,7 +247,11 @@ export const AppsPrivate = ({ myName, myAddress }) => {
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: error?.message || 'Unable to publish app', message:
error?.message ||
t('core:message.error.unable_publish_app', {
postProcess: 'capitalizeFirst',
}),
}); });
} }
}; };
@ -269,6 +305,7 @@ export const AppsPrivate = ({ myName, myAddress }) => {
<AppCircleLabel>Private</AppCircleLabel> <AppCircleLabel>Private</AppCircleLabel>
</AppCircleContainer> </AppCircleContainer>
</ButtonBase> </ButtonBase>
{isOpenPrivateModal && ( {isOpenPrivateModal && (
<Dialog <Dialog
open={isOpenPrivateModal} open={isOpenPrivateModal}
@ -342,8 +379,17 @@ export const AppsPrivate = ({ myName, myAddress }) => {
gap: '5px', gap: '5px',
}} }}
> >
<Label>Select a group</Label> <Label>
<Label>Only private groups will be shown</Label> {t('group:action.select_group', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Label>
{t('group:message.generic.only_private_groups', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
@ -358,7 +404,11 @@ export const AppsPrivate = ({ myName, myAddress }) => {
}); });
}} }}
> >
<MenuItem value={0}>No group selected</MenuItem> <MenuItem value={0}>
{t('group:message.generic.no_selection', {
postProcess: 'capitalizeFirst',
})}
</MenuItem>
{myGroupsPrivate {myGroupsPrivate
?.filter((item) => !item?.isOpen) ?.filter((item) => !item?.isOpen)
@ -371,7 +421,9 @@ export const AppsPrivate = ({ myName, myAddress }) => {
})} })}
</Select> </Select>
</Box> </Box>
<Spacer height="10px" /> <Spacer height="10px" />
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -380,7 +432,9 @@ export const AppsPrivate = ({ myName, myAddress }) => {
marginTop: '15px', marginTop: '15px',
}} }}
> >
<Label>name</Label> <Label>
{t('core:name', { postProcess: 'capitalizeFirst' })}
</Label>
<Input <Input
placeholder="name" placeholder="name"
value={privateAppValues?.name} value={privateAppValues?.name}
@ -394,6 +448,7 @@ export const AppsPrivate = ({ myName, myAddress }) => {
} }
/> />
</Box> </Box>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -402,9 +457,14 @@ export const AppsPrivate = ({ myName, myAddress }) => {
marginTop: '15px', marginTop: '15px',
}} }}
> >
<Label>identifier</Label> <Label>
{t('core:identifier', { postProcess: 'capitalizeFirst' })}
</Label>
<Input <Input
placeholder="identifier" placeholder={t('core:identifier', {
postProcess: 'capitalizeFirst',
})}
value={privateAppValues?.identifier} value={privateAppValues?.identifier}
onChange={(e) => onChange={(e) =>
setPrivateAppValues((prev) => { setPrivateAppValues((prev) => {
@ -425,7 +485,7 @@ export const AppsPrivate = ({ myName, myAddress }) => {
setIsOpenPrivateModal(false); setIsOpenPrivateModal(false);
}} }}
> >
Close {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
disabled={ disabled={
@ -438,7 +498,7 @@ export const AppsPrivate = ({ myName, myAddress }) => {
onClick={() => addPrivateApp()} onClick={() => addPrivateApp()}
autoFocus autoFocus
> >
Access {t('core:action.access', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</DialogActions> </DialogActions>
</> </>
@ -452,7 +512,9 @@ export const AppsPrivate = ({ myName, myAddress }) => {
fontSize: '14px', fontSize: '14px',
}} }}
> >
Select .zip file containing static content:{' '} {t('core:message.generic.select_zip', {
postProcess: 'capitalizeFirst',
})}
</PublishQAppInfo> </PublishQAppInfo>
<Spacer height="10px" /> <Spacer height="10px" />
@ -463,10 +525,11 @@ export const AppsPrivate = ({ myName, myAddress }) => {
fontSize: '14px', fontSize: '14px',
}} }}
>{` >{`
50mb MB maximum`}</PublishQAppInfo> 50mb MB max`}</PublishQAppInfo>
{file && ( {file && (
<> <>
<Spacer height="5px" /> <Spacer height="5px" />
<PublishQAppInfo>{`Selected: (${file?.name})`}</PublishQAppInfo> <PublishQAppInfo>{`Selected: (${file?.name})`}</PublishQAppInfo>
</> </>
)} )}
@ -482,7 +545,13 @@ export const AppsPrivate = ({ myName, myAddress }) => {
> >
{' '} {' '}
<input {...getInputProps()} /> <input {...getInputProps()} />
{file ? 'Change' : 'Choose'} File {file
? t('core:action.change_file', {
postProcess: 'capitalizeFirst',
})
: t('core:action.choose_file', {
postProcess: 'capitalizeFirst',
})}
</PublishQAppChoseFile> </PublishQAppChoseFile>
<Spacer height="20px" /> <Spacer height="20px" />
@ -521,10 +590,18 @@ export const AppsPrivate = ({ myName, myAddress }) => {
gap: '5px', gap: '5px',
}} }}
> >
<Label>Select a group</Label>
<Label> <Label>
Only groups where you are an admin will be shown {t('group:action.select_group', {
postProcess: 'capitalizeFirst',
})}
</Label> </Label>
<Label>
{t('group:amessage.generic.admin_only', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
@ -532,7 +609,11 @@ export const AppsPrivate = ({ myName, myAddress }) => {
label="Groups where you are an admin" label="Groups where you are an admin"
onChange={(e) => setSelectedGroup(e.target.value)} onChange={(e) => setSelectedGroup(e.target.value)}
> >
<MenuItem value={0}>No group selected</MenuItem> <MenuItem value={0}>
{t('group:message.generic.no_selection', {
postProcess: 'capitalizeFirst',
})}
</MenuItem>
{myGroupsWhereIAmAdmin {myGroupsWhereIAmAdmin
?.filter((item) => !item?.isOpen) ?.filter((item) => !item?.isOpen)
.map((group) => { .map((group) => {
@ -555,9 +636,13 @@ export const AppsPrivate = ({ myName, myAddress }) => {
marginTop: '15px', marginTop: '15px',
}} }}
> >
<Label>identifier</Label> <Label>
{t('core:identifier', { postProcess: 'capitalizeFirst' })}
</Label>
<Input <Input
placeholder="identifier" placeholder={t('core:identifier', {
postProcess: 'capitalizeFirst',
})}
value={newPrivateAppValues?.identifier} value={newPrivateAppValues?.identifier}
onChange={(e) => onChange={(e) =>
setNewPrivateAppValues((prev) => { setNewPrivateAppValues((prev) => {
@ -580,9 +665,14 @@ export const AppsPrivate = ({ myName, myAddress }) => {
marginTop: '15px', marginTop: '15px',
}} }}
> >
<Label>App name</Label> <Label>
{t('core:app_name', { postProcess: 'capitalizeFirst' })}
</Label>
<Input <Input
placeholder="App name" placeholder={t('core:app_name', {
postProcess: 'capitalizeFirst',
})}
value={newPrivateAppValues?.name} value={newPrivateAppValues?.name}
onChange={(e) => onChange={(e) =>
setNewPrivateAppValues((prev) => { setNewPrivateAppValues((prev) => {
@ -598,10 +688,15 @@ export const AppsPrivate = ({ myName, myAddress }) => {
<Spacer height="10px" /> <Spacer height="10px" />
<ImageUploader onPick={(file) => setLogo(file)}> <ImageUploader onPick={(file) => setLogo(file)}>
<Button variant="contained">Choose logo</Button> <Button variant="contained">
{t('core:action.choose_logo', {
postProcess: 'capitalizeFirst',
})}
</Button>
</ImageUploader> </ImageUploader>
{logo?.name} {logo?.name}
<Spacer height="25px" /> <Spacer height="25px" />
</DialogContent> </DialogContent>
@ -613,7 +708,7 @@ export const AppsPrivate = ({ myName, myAddress }) => {
clearFields(); clearFields();
}} }}
> >
Close {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -627,7 +722,7 @@ export const AppsPrivate = ({ myName, myAddress }) => {
onClick={() => publishPrivateApp()} onClick={() => publishPrivateApp()}
autoFocus autoFocus
> >
Publish {t('core:action.publish', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</DialogActions> </DialogActions>
</> </>

View File

@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { getBaseApiReact, MyContext } from '../../App'; import { getBaseApiReact, MyContext } from '../../App';
import { createEndpoint } from '../../background'; import { createEndpoint } from '../../background';
@ -7,10 +7,9 @@ import {
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from '../../atoms/global'; } from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop'; import { saveToLocalStorage } from './AppsNavBarDesktop';
import { base64ToBlobUrl } from '../../utils/fileReading';
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption'; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import { useAtom, useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
export const useHandlePrivateApps = () => { export const useHandlePrivateApps = () => {
const [status, setStatus] = useState(''); const [status, setStatus] = useState('');

View File

@ -1,7 +1,7 @@
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useCallback, useContext, useEffect, useState } from 'react';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { navigationControllerAtom } from '../../atoms/global'; import { navigationControllerAtom } from '../../atoms/global';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'; import { Filesystem, Directory } from '@capacitor/filesystem';
import { saveFile } from '../../qortalRequests/get'; import { saveFile } from '../../qortalRequests/get';
import { mimeToExtensionMap } from '../../utils/memeTypes'; import { mimeToExtensionMap } from '../../utils/memeTypes';
import { MyContext } from '../../App'; import { MyContext } from '../../App';

View File

@ -76,14 +76,16 @@ export const DownloadWallet = ({
if (!keepCurrentPassword && !newPassword) { if (!keepCurrentPassword && !newPassword) {
setWalletToBeDownloadedError( setWalletToBeDownloadedError(
t('auth:wallet.error.missing_new_password', { t('auth:wallet.error.missing_new_password', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
); );
return; return;
} }
if (!walletToBeDownloadedPassword) { if (!walletToBeDownloadedPassword) {
setWalletToBeDownloadedError( setWalletToBeDownloadedError(
t('auth:wallet.error.missing_password', { postProcess: 'capitalize' }) t('auth:wallet.error.missing_password', {
postProcess: 'capitalizeFirst',
})
); );
return; return;
} }
@ -157,7 +159,9 @@ export const DownloadWallet = ({
fontWeight: 600, fontWeight: 600,
}} }}
> >
{t('auth:download_account', { postProcess: 'capitalize' })} {t('auth:action.download_account', {
postProcess: 'capitalizeFirst',
})}
</TextP> </TextP>
</Box> </Box>
@ -167,7 +171,7 @@ export const DownloadWallet = ({
<> <>
<CustomLabel htmlFor="standard-adornment-password"> <CustomLabel htmlFor="standard-adornment-password">
{t('auth:wallet.password_confirmation', { {t('auth:wallet.password_confirmation', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</CustomLabel> </CustomLabel>
@ -206,34 +210,38 @@ export const DownloadWallet = ({
<Box sx={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography sx={{ fontSize: '14px' }}> <Typography sx={{ fontSize: '14px' }}>
{t('auth:wallet.keep_password', { {t('auth:wallet.keep_password', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</Box> </Box>
} }
/> />
<Spacer height="20px" /> <Spacer height="20px" />
{!keepCurrentPassword && ( {!keepCurrentPassword && (
<> <>
<CustomLabel htmlFor="standard-adornment-password"> <CustomLabel htmlFor="standard-adornment-password">
{t('auth:wallet.new_password', { {t('auth:wallet.new_password', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</CustomLabel> </CustomLabel>
<Spacer height="5px" /> <Spacer height="5px" />
<PasswordField <PasswordField
id="standard-adornment-password" id="standard-adornment-password"
value={newPassword} value={newPassword}
onChange={(e) => setNewPassword(e.target.value)} onChange={(e) => setNewPassword(e.target.value)}
/> />
<Spacer height="20px" /> <Spacer height="20px" />
</> </>
)} )}
<CustomButton onClick={confirmPasswordToDownload}> <CustomButton onClick={confirmPasswordToDownload}>
{t('auth:password_confirmation', { {t('auth:password_confirmation', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</CustomButton> </CustomButton>
@ -247,14 +255,14 @@ export const DownloadWallet = ({
onClick={async () => { onClick={async () => {
await saveFileToDiskFunc(); await saveFileToDiskFunc();
await showInfo({ await showInfo({
message: t('auth:keep_secure', { message: t('auth:message.generic.keep_secure', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
}} }}
> >
{t('auth:download_account', { {t('auth:action.download_account', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</CustomButton> </CustomButton>
</> </>

View File

@ -1,7 +1,7 @@
import { useContext, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { MyContext } from '../../App';
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { AdminSpaceInner } from './AdminSpaceInner'; import { AdminSpaceInner } from './AdminSpaceInner';
import { useTranslation } from 'react-i18next';
export const AdminSpace = ({ export const AdminSpace = ({
selectedGroup, selectedGroup,
@ -19,6 +19,8 @@ export const AdminSpace = ({
isOwner, isOwner,
}) => { }) => {
const [isMoved, setIsMoved] = useState(false); const [isMoved, setIsMoved] = useState(false);
const { t } = useTranslation(['core', 'group']);
useEffect(() => { useEffect(() => {
if (hide) { if (hide) {
setTimeout(() => setIsMoved(true), 300); // Wait for the fade-out to complete before moving setTimeout(() => setIsMoved(true), 300); // Wait for the fade-out to complete before moving
@ -35,10 +37,10 @@ export const AdminSpace = ({
height: 'calc(100vh - 70px)', height: 'calc(100vh - 70px)',
left: hide && '-1000px', left: hide && '-1000px',
opacity: hide ? 0 : 1, opacity: hide ? 0 : 1,
overflow: 'auto',
position: hide ? 'fixed' : 'relative', position: hide ? 'fixed' : 'relative',
visibility: hide && 'hidden', visibility: hide && 'hidden',
width: '100%', width: '100%',
overflow: 'auto',
}} }}
> >
{!isAdmin && ( {!isAdmin && (
@ -50,9 +52,14 @@ export const AdminSpace = ({
width: '100%', width: '100%',
}} }}
> >
<Typography>Sorry, this space is only for Admins.</Typography> <Typography>
{t('core:message.generic.space_for_admins', {
postProcess: 'capitalizeFirst',
})}
</Typography>
</Box> </Box>
)} )}
{isAdmin && ( {isAdmin && (
<AdminSpaceInner <AdminSpaceInner
setIsForceShowCreationKeyPopup={setIsForceShowCreationKeyPopup} setIsForceShowCreationKeyPopup={setIsForceShowCreationKeyPopup}

View File

@ -15,7 +15,9 @@ import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import { formatTimestampForum } from '../../utils/time'; import { formatTimestampForum } from '../../utils/time';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { GroupAvatar } from '../GroupAvatar'; import { GroupAvatar } from './GroupAvatar';
import { useTranslation } from 'react-i18next';
import i18next from 'i18next';
export const getPublishesFromAdminsAdminSpace = async ( export const getPublishesFromAdminsAdminSpace = async (
admins: string[], admins: string[],
@ -26,7 +28,7 @@ export const getPublishesFromAdminsAdminSpace = async (
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error('network error'); throw new Error(i18next.t('core:message.error.network_generic'));
} }
const adminData = await response.json(); const adminData = await response.json();
@ -72,6 +74,7 @@ export const AdminSpaceInner = ({
const [isLoadingPublishKey, setIsLoadingPublishKey] = useState(false); const [isLoadingPublishKey, setIsLoadingPublishKey] = useState(false);
const { show, setInfoSnackCustom, setOpenSnackGlobal } = const { show, setInfoSnackCustom, setOpenSnackGlobal } =
useContext(MyContext); useContext(MyContext);
const { t } = useTranslation(['auth', 'core', 'group']);
const getAdminGroupSecretKey = useCallback(async () => { const getAdminGroupSecretKey = useCallback(async () => {
try { try {
@ -81,20 +84,24 @@ export const AdminSpaceInner = ({
selectedGroup selectedGroup
); );
if (getLatestPublish === false) return; if (getLatestPublish === false) return;
let data;
const res = await fetch( const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${ `${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${
getLatestPublish.name getLatestPublish.name
}/${getLatestPublish.identifier}?encoding=base64&rebuild=true` }/${getLatestPublish.identifier}?encoding=base64&rebuild=true`
); );
data = await res.text();
const data = await res.text();
const decryptedKey: any = await decryptResource(data); const decryptedKey: any = await decryptResource(data);
const dataint8Array = base64ToUint8Array(decryptedKey.data); const dataint8Array = base64ToUint8Array(decryptedKey.data);
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
if (!validateSecretKey(decryptedKeyToObject)) if (!validateSecretKey(decryptedKeyToObject))
throw new Error('SecretKey is not valid'); throw new Error(
t('auth:message.error.invalid_secret_key', {
postProcess: 'capitalizeFirst',
})
);
setAdminGroupSecretKey(decryptedKeyToObject); setAdminGroupSecretKey(decryptedKeyToObject);
setAdminGroupSecretKeyPublishDetails(getLatestPublish); setAdminGroupSecretKeyPublishDetails(getLatestPublish);
} catch (error) { } catch (error) {
@ -125,7 +132,10 @@ export const AdminSpaceInner = ({
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
message: 'Would you like to perform an ARBITRARY transaction?', message: t('core:message.question.perform_transaction', {
action: 'ARBITRARY',
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -141,22 +151,31 @@ export const AdminSpaceInner = ({
if (!response?.error) { if (!response?.error) {
setInfoSnackCustom({ setInfoSnackCustom({
type: 'success', type: 'success',
message: message: t('auth:message.success.reencrypted_secret_key', {
'Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.', postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
return; return;
} }
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: response?.error || 'unable to re-encrypt secret key', message:
response?.error ||
t('auth:message.error.unable_reencrypt_secret_key', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
}) })
.catch((error) => { .catch((error) => {
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: error?.message || 'unable to re-encrypt secret key', message:
error?.message ||
t('auth:message.error.unable_reencrypt_secret_key', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
}); });
@ -184,10 +203,13 @@ export const AdminSpaceInner = ({
fontSize: '14px', fontSize: '14px',
}} }}
> >
Reminder: After publishing the key, it will take a couple of minutes for {t('auth:message.error.publishing_key', {
it to appear. Please just wait. postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
<Spacer height="25px" /> <Spacer height="25px" />
<Box <Box
sx={{ sx={{
border: '1px solid gray', border: '1px solid gray',
@ -201,28 +223,43 @@ export const AdminSpaceInner = ({
}} }}
> >
{isFetchingGroupSecretKey && ( {isFetchingGroupSecretKey && (
<Typography>Fetching Group secret key publishes</Typography>
)}
{!isFetchingGroupSecretKey &&
groupSecretKeyPublishDetails === false && (
<Typography>No secret key published yet</Typography>
)}
{groupSecretKeyPublishDetails && (
<Typography> <Typography>
Last encryption date:{' '} {t('auth:message.generic.fetching_group_secret_key', {
{formatTimestampForum( postProcess: 'capitalizeFirst',
groupSecretKeyPublishDetails?.updated || })}
groupSecretKeyPublishDetails?.created
)}{' '}
{` by ${groupSecretKeyPublishDetails?.name}`}
</Typography> </Typography>
)} )}
{!isFetchingGroupSecretKey &&
groupSecretKeyPublishDetails === false && (
<Typography>
{t('auth:message.generic.no_secret_key_published', {
postProcess: 'capitalizeFirst',
})}
</Typography>
)}
{groupSecretKeyPublishDetails && (
<Typography>
{t('auth:message.generic.last_encryption_date', {
date: formatTimestampForum(
groupSecretKeyPublishDetails?.updated ||
groupSecretKeyPublishDetails?.created
),
name: groupSecretKeyPublishDetails?.name,
postProcess: 'capitalizeFirst',
})}
</Typography>
)}
<Button <Button
disabled={isFetchingGroupSecretKey} disabled={isFetchingGroupSecretKey}
onClick={() => setIsForceShowCreationKeyPopup(true)} onClick={() => setIsForceShowCreationKeyPopup(true)}
variant="contained" variant="contained"
> >
Publish group secret key {t('auth:action.publish_group_secret_key', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Spacer height="20px" /> <Spacer height="20px" />
@ -232,9 +269,9 @@ export const AdminSpaceInner = ({
fontSize: '14px', fontSize: '14px',
}} }}
> >
This key is to encrypt GROUP related content. This is the only one {t('auth:tips.key_encrypt_group', {
used in this UI as of now. All group members will be able to see postProcess: 'capitalizeFirst',
content encrypted with this key. })}
</Typography> </Typography>
</Box> </Box>
@ -253,26 +290,41 @@ export const AdminSpaceInner = ({
}} }}
> >
{isFetchingAdminGroupSecretKey && ( {isFetchingAdminGroupSecretKey && (
<Typography>Fetching Admins secret key</Typography>
)}
{!isFetchingAdminGroupSecretKey && !adminGroupSecretKey && (
<Typography>No secret key published yet</Typography>
)}
{adminGroupSecretKeyPublishDetails && (
<Typography> <Typography>
Last encryption date:{' '} {t('auth:message.generic.fetching_admin_secret_key', {
{formatTimestampForum( postProcess: 'capitalizeFirst',
adminGroupSecretKeyPublishDetails?.updated || })}
adminGroupSecretKeyPublishDetails?.created
)}
</Typography> </Typography>
)} )}
{!isFetchingAdminGroupSecretKey && !adminGroupSecretKey && (
<Typography>
{t('auth:message.generic.no_secret_key_published', {
postProcess: 'capitalizeFirst',
})}
</Typography>
)}
{adminGroupSecretKeyPublishDetails && (
<Typography>
{t('auth:message.generic.last_encryption_date', {
date: formatTimestampForum(
adminGroupSecretKeyPublishDetails?.updated ||
adminGroupSecretKeyPublishDetails?.created
),
postProcess: 'capitalizeFirst',
})}
</Typography>
)}
<Button <Button
disabled={isFetchingAdminGroupSecretKey} disabled={isFetchingAdminGroupSecretKey}
onClick={createCommonSecretForAdmins} onClick={createCommonSecretForAdmins}
variant="contained" variant="contained"
> >
Publish admin secret key {t('auth:action.publish_admin_secret_key', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Spacer height="20px" /> <Spacer height="20px" />
@ -282,11 +334,14 @@ export const AdminSpaceInner = ({
fontSize: '14px', fontSize: '14px',
}} }}
> >
This key is to encrypt ADMIN related content. Only admins would see {t('auth:tips.key_encrypt_admin', {
content encrypted with it. postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
<Spacer height="25px" /> <Spacer height="25px" />
{isOwner && ( {isOwner && (
<Box <Box
sx={{ sx={{
@ -301,7 +356,11 @@ export const AdminSpaceInner = ({
alignItems: 'center', alignItems: 'center',
}} }}
> >
<Typography>Group Avatar</Typography> <Typography>
{t('group:group.avatar', {
postProcess: 'capitalizeFirst',
})}
</Typography>
<GroupAvatar <GroupAvatar
setOpenSnack={setOpenSnackGlobal} setOpenSnack={setOpenSnackGlobal}

View File

@ -1,4 +1,4 @@
import React, { useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import TipTap from './TipTap'; import TipTap from './TipTap';
import { import {
AuthenticatedContainerInnerTop, AuthenticatedContainerInnerTop,
@ -8,7 +8,7 @@ import { Box, CircularProgress, useTheme } from '@mui/material';
import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { objectToBase64 } from '../../qdn/encryption/group-encryption';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { getBaseApi, getFee } from '../../background'; import { getFee } from '../../background';
import { import {
decryptPublishes, decryptPublishes,
getTempPublish, getTempPublish,
@ -24,6 +24,7 @@ import {
pauseAllQueues, pauseAllQueues,
resumeAllQueues, resumeAllQueues,
} from '../../App'; } from '../../App';
import { useTranslation } from 'react-i18next';
const tempKey = 'accouncement-comment'; const tempKey = 'accouncement-comment';
@ -39,10 +40,10 @@ export const AnnouncementDiscussion = ({
isPrivate, isPrivate,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const [isSending, setIsSending] = useState(false); const [isSending, setIsSending] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isFocusedParent, setIsFocusedParent] = useState(false); const [isFocusedParent, setIsFocusedParent] = useState(false);
const [comments, setComments] = useState([]); const [comments, setComments] = useState([]);
const [tempPublishedList, setTempPublishedList] = useState([]); const [tempPublishedList, setTempPublishedList] = useState([]);
const firstMountRef = useRef(false); const firstMountRef = useRef(false);
@ -100,7 +101,12 @@ export const AnnouncementDiscussion = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
@ -131,9 +137,13 @@ export const AnnouncementDiscussion = ({
pauseAllQueues(); pauseAllQueues();
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
message: 'Would you like to perform a ARBITRARY transaction?', message: t('core:message.question.perform_transaction', {
action: 'ARBITRARY',
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
if (isSending) return; if (isSending) return;
if (editorRef.current) { if (editorRef.current) {
const htmlContent = editorRef.current.getHTML(); const htmlContent = editorRef.current.getHTML();
@ -155,10 +165,11 @@ export const AnnouncementDiscussion = ({
: await encryptChatMessage(message64, secretKeyObject); : await encryptChatMessage(message64, secretKeyObject);
const randomUid = uid.rnd(); const randomUid = uid.rnd();
const identifier = `cm-${selectedAnnouncement.identifier}-${randomUid}`; const identifier = `cm-${selectedAnnouncement.identifier}-${randomUid}`;
const res = await publishAnc({ const res = await publishAnc({
encryptedData: encryptSingle, encryptedData: encryptSingle,
identifier, identifier,
}); }); // TODO remove unused?
const dataToSaveToStorage = { const dataToSaveToStorage = {
name: myName, name: myName,
@ -168,12 +179,13 @@ export const AnnouncementDiscussion = ({
created: Date.now(), created: Date.now(),
announcementId: selectedAnnouncement.identifier, announcementId: selectedAnnouncement.identifier,
}; };
await saveTempPublish({ data: dataToSaveToStorage, key: tempKey }); await saveTempPublish({ data: dataToSaveToStorage, key: tempKey });
setTempData(); setTempData();
clearEditorContent(); clearEditorContent();
} }
// send chat message // TODO send chat message
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally { } finally {
@ -182,14 +194,12 @@ export const AnnouncementDiscussion = ({
} }
}; };
const getComments = React.useCallback( const getComments = useCallback(
async (selectedAnnouncement, isPrivate) => { async (selectedAnnouncement, isPrivate) => {
try { try {
setIsLoading(true); setIsLoading(true);
const offset = 0; const offset = 0;
// dispatch(setIsLoadingGlobal(true))
const identifier = `cm-${selectedAnnouncement.identifier}`; const identifier = `cm-${selectedAnnouncement.identifier}`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`; const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
const response = await fetch(url, { const response = await fetch(url, {
@ -209,8 +219,6 @@ export const AnnouncementDiscussion = ({
console.log(error); console.log(error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
// dispatch(setIsLoadingGlobal(false))
} }
}, },
[secretKey] [secretKey]
@ -259,7 +267,7 @@ export const AnnouncementDiscussion = ({
return sortedList; return sortedList;
}, [tempPublishedList, comments]); }, [tempPublishedList, comments]);
React.useEffect(() => { useEffect(() => {
if (!secretKey && isPrivate) return; if (!secretKey && isPrivate) return;
if (selectedAnnouncement && !firstMountRef.current && isPrivate !== null) { if (selectedAnnouncement && !firstMountRef.current && isPrivate !== null) {
getComments(selectedAnnouncement, isPrivate); getComments(selectedAnnouncement, isPrivate);
@ -299,6 +307,7 @@ export const AnnouncementDiscussion = ({
<Spacer height="20px" /> <Spacer height="20px" />
</div> </div>
<AnnouncementList <AnnouncementList
announcementData={data} announcementData={data}
initialMessages={combinedListTempAndReal} initialMessages={combinedListTempAndReal}
@ -308,6 +317,7 @@ export const AnnouncementDiscussion = ({
loadMore={loadMore} loadMore={loadMore}
myName={myName} myName={myName}
/> />
<div <div
style={{ style={{
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
@ -343,6 +353,7 @@ export const AnnouncementDiscussion = ({
setIsFocusedParent={setIsFocusedParent} setIsFocusedParent={setIsFocusedParent}
/> />
</div> </div>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -359,7 +370,7 @@ export const AnnouncementDiscussion = ({
if (isSending) return; if (isSending) return;
setIsFocusedParent(false); setIsFocusedParent(false);
clearEditorContent(); clearEditorContent();
// Unfocus the editor // TODO Unfocus the editor
}} }}
style={{ style={{
alignSelf: 'center', alignSelf: 'center',
@ -371,7 +382,7 @@ export const AnnouncementDiscussion = ({
padding: '5px', padding: '5px',
}} }}
> >
{` Close`} {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</CustomButton> </CustomButton>
)} )}
<CustomButton <CustomButton
@ -402,7 +413,9 @@ export const AnnouncementDiscussion = ({
}} }}
/> />
)} )}
{` Publish Comment`} {t('core:action.publish_comment', {
postProcess: 'capitalizeFirst',
})}
</CustomButton> </CustomButton>
</Box> </Box>
</div> </div>
@ -410,7 +423,9 @@ export const AnnouncementDiscussion = ({
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{
message: 'Loading comments... please wait.', message: t('core:loading.comments', {
postProcess: 'capitalizeFirst',
}),
}} }}
/> />
</div> </div>

View File

@ -1,14 +1,14 @@
import React, { useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { MessageDisplay } from './MessageDisplay'; import { MessageDisplay } from './MessageDisplay';
import { Avatar, Box, Typography, useTheme } from '@mui/material'; import { Avatar, Box, Typography, useTheme } from '@mui/material';
import { formatTimestamp } from '../../utils/time'; import { formatTimestamp } from '../../utils/time';
import ChatBubbleIcon from '@mui/icons-material/ChatBubble'; import ChatBubbleIcon from '@mui/icons-material/ChatBubble';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import { getBaseApi } from '../../background';
import { requestQueueCommentCount } from './GroupAnnouncements'; import { requestQueueCommentCount } from './GroupAnnouncements';
import { CustomLoader } from '../../common/CustomLoader'; import { CustomLoader } from '../../common/CustomLoader';
import { getArbitraryEndpointReact, getBaseApiReact } from '../../App'; import { getArbitraryEndpointReact, getBaseApiReact } from '../../App';
import { WrapperUserAction } from '../WrapperUserAction'; import { WrapperUserAction } from '../WrapperUserAction';
import { useTranslation } from 'react-i18next';
export const AnnouncementItem = ({ export const AnnouncementItem = ({
message, message,
@ -18,12 +18,12 @@ export const AnnouncementItem = ({
myName, myName,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const [commentLength, setCommentLength] = useState(0); const [commentLength, setCommentLength] = useState(0);
const getNumberOfComments = React.useCallback(async () => {
const getNumberOfComments = useCallback(async () => {
try { try {
const offset = 0; const offset = 0;
// dispatch(setIsLoadingGlobal(true))
const identifier = `cm-${message.identifier}`; const identifier = `cm-${message.identifier}`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`; const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
@ -84,6 +84,7 @@ export const AnnouncementItem = ({
{message?.name?.charAt(0)} {message?.name?.charAt(0)}
</Avatar> </Avatar>
</WrapperUserAction> </WrapperUserAction>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -106,6 +107,7 @@ export const AnnouncementItem = ({
{message?.name} {message?.name}
</Typography> </Typography>
</WrapperUserAction> </WrapperUserAction>
{!messageData?.decryptedData && ( {!messageData?.decryptedData && (
<Box <Box
sx={{ sx={{
@ -117,6 +119,7 @@ export const AnnouncementItem = ({
<CustomLoader /> <CustomLoader />
</Box> </Box>
)} )}
{messageData?.decryptedData?.message && ( {messageData?.decryptedData?.message && (
<> <>
{messageData?.type === 'notification' ? ( {messageData?.type === 'notification' ? (
@ -150,6 +153,7 @@ export const AnnouncementItem = ({
</Box> </Box>
</Box> </Box>
</Box> </Box>
{!disableComment && ( {!disableComment && (
<Box <Box
sx={{ sx={{
@ -189,10 +193,13 @@ export const AnnouncementItem = ({
fontSize: '14px', fontSize: '14px',
}} }}
> >
Leave comment {t('core:action.leave_comment', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}
</Box> </Box>
<ArrowForwardIosIcon <ArrowForwardIosIcon
sx={{ sx={{
fontSize: '20px', fontSize: '20px',

View File

@ -3,6 +3,7 @@ import { CellMeasurerCache } from 'react-virtualized';
import { AnnouncementItem } from './AnnouncementItem'; import { AnnouncementItem } from './AnnouncementItem';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import { CustomButton } from '../../styles/App-styles'; import { CustomButton } from '../../styles/App-styles';
import { useTranslation } from 'react-i18next';
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
@ -18,8 +19,8 @@ export const AnnouncementList = ({
loadMore, loadMore,
myName, myName,
}) => { }) => {
const listRef = useRef(null);
const [messages, setMessages] = useState(initialMessages); const [messages, setMessages] = useState(initialMessages);
const { t } = useTranslation(['auth', 'core', 'group']);
useEffect(() => { useEffect(() => {
cache.clearAll(); cache.clearAll();
@ -36,9 +37,9 @@ export const AnnouncementList = ({
flexDirection: 'column', flexDirection: 'column',
flexGrow: 1, flexGrow: 1,
flexShrink: 1, flexShrink: 1,
overflow: 'auto',
position: 'relative', position: 'relative',
width: '100%', width: '100%',
overflow: 'auto',
}} }}
> >
{messages.map((message) => { {messages.map((message) => {
@ -69,19 +70,7 @@ export const AnnouncementList = ({
</div> </div>
); );
})} })}
{/* <AutoSizer>
{({ height, width }) => (
<List
ref={listRef}
width={width}
height={height}
rowCount={messages.length}
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
deferredMeasurementCache={cache}
/>
)}
</AutoSizer> */}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -92,7 +81,9 @@ export const AnnouncementList = ({
> >
{showLoadMore && ( {showLoadMore && (
<CustomButton onClick={loadMore}> <CustomButton onClick={loadMore}>
Load older announcements {t('core:action.load_announcements', {
postProcess: 'capitalizeFirst',
})}
</CustomButton> </CustomButton>
)} )}
</Box> </Box>

View File

@ -1,12 +1,4 @@
import React, { import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react';
import { ChatList } from './ChatList'; import { ChatList } from './ChatList';
import Tiptap from './TipTap'; import Tiptap from './TipTap';
import { CustomButton } from '../../styles/App-styles'; import { CustomButton } from '../../styles/App-styles';
@ -31,9 +23,9 @@ import {
} from '../../utils/events'; } from '../../utils/events';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
import { ExitIcon } from '../../assets/Icons/ExitIcon'; import { ExitIcon } from '../../assets/Icons/ExitIcon';
import { ReplyPreview } from './MessageItem'; import { ReplyPreview } from './MessageItem';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 5 }); const uid = new ShortUniqueId({ length: 5 });
@ -50,21 +42,20 @@ export const ChatDirect = ({
setMobileViewModeKeepOpen, setMobileViewModeKeepOpen,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue(); const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
const [isFocusedParent, setIsFocusedParent] = useState(false); const [isFocusedParent, setIsFocusedParent] = useState(false);
const [onEditMessage, setOnEditMessage] = useState(null); const [onEditMessage, setOnEditMessage] = useState(null);
const [messages, setMessages] = useState([]); const [messages, setMessages] = useState([]);
const [isSending, setIsSending] = useState(false); const [isSending, setIsSending] = useState(false);
const [directToValue, setDirectToValue] = useState(''); const [directToValue, setDirectToValue] = useState('');
const hasInitialized = useRef(false); const hasInitialized = useRef(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = React.useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [publicKeyOfRecipient, setPublicKeyOfRecipient] = React.useState(''); const [publicKeyOfRecipient, setPublicKeyOfRecipient] = useState('');
const hasInitializedWebsocket = useRef(false); const hasInitializedWebsocket = useRef(false);
const [chatReferences, setChatReferences] = useState({}); const [chatReferences, setChatReferences] = useState({});
const editorRef = useRef(null); const editorRef = useRef(null);
const socketRef = useRef(null); const socketRef = useRef(null);
const timeoutIdRef = useRef(null); const timeoutIdRef = useRef(null);
@ -74,12 +65,8 @@ export const ChatDirect = ({
const setEditorRef = (editorInstance) => { const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance; editorRef.current = editorInstance;
}; };
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state
};
const publicKeyOfRecipientRef = useRef(null); const publicKeyOfRecipientRef = useRef(null);
const getPublicKeyFunc = async (address) => { const getPublicKeyFunc = async (address) => {
try { try {
const publicKey = await getPublicKey(address); const publicKey = await getPublicKey(address);
@ -229,7 +216,12 @@ export const ChatDirect = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
@ -397,11 +389,20 @@ export const ChatDirect = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
throw new Error(error); if (error instanceof Error) {
throw new Error(error.message);
} else {
throw new Error(String(error));
}
} }
}; };
const clearEditorContent = () => { const clearEditorContent = () => {
@ -432,8 +433,14 @@ export const ChatDirect = ({
try { try {
if (messageSize > 4000) return; if (messageSize > 4000) return;
// TODO set magic number in a proper file
if (+balance < 4) if (+balance < 4)
throw new Error('You need at least 4 QORT to send a message'); throw new Error(
t('group:message.error.qortals_required', {
quantity: 4,
postProcess: 'capitalizeFirst',
})
);
if (isSending) return; if (isSending) return;
if (editorRef.current) { if (editorRef.current) {
const htmlContent = editorRef.current.getHTML(); const htmlContent = editorRef.current.getHTML();
@ -500,7 +507,10 @@ export const ChatDirect = ({
type: 'error', type: 'error',
message: message:
errorMsg === 'invalid signature' errorMsg === 'invalid signature'
? 'You need at least 4 QORT to send a message' ? t('group:message.error.qortals_required', {
quantity: 4,
postProcess: 'capitalizeFirst',
})
: errorMsg, : errorMsg,
}); });
setOpenSnack(true); setOpenSnack(true);
@ -566,13 +576,14 @@ export const ChatDirect = ({
fontSize: '14px', fontSize: '14px',
}} }}
> >
Close Direct Chat {t('core:action.close_chat', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</Box> </Box>
{isNewChat && ( {isNewChat && (
<> <>
<Spacer height="30px" /> <Spacer height="30px" />
<Input <Input
sx={{ sx={{
fontSize: '18px', fontSize: '18px',
@ -686,13 +697,19 @@ export const ChatDirect = ({
width: '100%', width: '100%',
}} }}
> >
<Typography <Typography // TODO set magic number in a proper file
sx={{ sx={{
fontSize: '12px', fontSize: '12px',
color: color:
messageSize > 4000 ? theme.palette.other.danger : 'unset', messageSize > 4000 ? theme.palette.other.danger : 'unset',
}} }}
>{`Your message size is of ${messageSize} bytes out of a maximum of 4000`}</Typography> >
{t('core:message.error.message_size', {
maximum: 4000,
size: messageSize,
postProcess: 'capitalizeFirst',
})}
</Typography>
</Box> </Box>
)} )}
</div> </div>
@ -746,7 +763,7 @@ export const ChatDirect = ({
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{
message: 'Loading chat... please wait.', message: t('core:loading.chat', { postProcess: 'capitalizeFirst' }),
}} }}
/> />

View File

@ -1,4 +1,4 @@
import React, { import {
useCallback, useCallback,
useContext, useContext,
useEffect, useEffect,
@ -50,6 +50,8 @@ import CloseIcon from '@mui/icons-material/Close';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import ImageIcon from '@mui/icons-material/Image'; import ImageIcon from '@mui/icons-material/Image';
import { messageHasImage } from '../../utils/chat'; import { messageHasImage } from '../../utils/chat';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 5 }); const uid = new ShortUniqueId({ length: 5 });
const uidImages = new ShortUniqueId({ length: 12 }); const uidImages = new ShortUniqueId({ length: 12 });
@ -75,8 +77,8 @@ export const ChatGroup = ({
const [isSending, setIsSending] = useState(false); const [isSending, setIsSending] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isMoved, setIsMoved] = useState(false); const [isMoved, setIsMoved] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = React.useState(null); const [infoSnack, setInfoSnack] = useState(null);
const hasInitialized = useRef(false); const hasInitialized = useRef(false);
const [isFocusedParent, setIsFocusedParent] = useState(false); const [isFocusedParent, setIsFocusedParent] = useState(false);
const [replyMessage, setReplyMessage] = useState(null); const [replyMessage, setReplyMessage] = useState(null);
@ -93,8 +95,8 @@ export const ChatGroup = ({
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue(); const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
const lastReadTimestamp = useRef(null); const lastReadTimestamp = useRef(null);
const handleUpdateRef = useRef(null); const handleUpdateRef = useRef(null);
const { t } = useTranslation(['auth', 'core', 'group']);
const getTimestampEnterChat = async (selectedGroup) => { const getTimestampEnterChat = async (selectedGroup) => {
try { try {
@ -129,7 +131,12 @@ export const ChatGroup = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
@ -154,9 +161,6 @@ export const ChatGroup = ({
return Array.from(uniqueMembers); return Array.from(uniqueMembers);
}, [messages]); }, [messages]);
const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state
};
const setEditorRef = (editorInstance) => { const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance; editorRef.current = editorInstance;
}; };
@ -168,6 +172,7 @@ export const ChatGroup = ({
} }
return []; return [];
}, [selectedGroup, queueChats]); }, [selectedGroup, queueChats]);
const tempChatReferences = useMemo(() => { const tempChatReferences = useMemo(() => {
if (!selectedGroup) return []; if (!selectedGroup) return [];
if (queueChats[selectedGroup]) { if (queueChats[selectedGroup]) {
@ -184,12 +189,6 @@ export const ChatGroup = ({
} }
}, [secretKey]); }, [secretKey]);
// const getEncryptedSecretKey = useCallback(()=> {
// const response = getResource()
// const decryptResponse = decryptResource()
// return
// }, [])
const checkForFirstSecretKeyNotification = (messages) => { const checkForFirstSecretKeyNotification = (messages) => {
messages?.forEach((message) => { messages?.forEach((message) => {
try { try {
@ -250,7 +249,6 @@ export const ChatGroup = ({
const dataRemovedBlock = responseData?.filter((item) => { const dataRemovedBlock = responseData?.filter((item) => {
return !isUserBlocked(item?.sender, item?.senderName); return !isUserBlocked(item?.sender, item?.senderName);
}); });
decryptMessages(dataRemovedBlock, false); decryptMessages(dataRemovedBlock, false);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -273,6 +271,7 @@ export const ChatGroup = ({
const filterUIMessages = encryptedMessages.filter( const filterUIMessages = encryptedMessages.filter(
(item) => !isExtMsg(item.data) (item) => !isExtMsg(item.data)
); );
const decodedUIMessages = const decodedUIMessages =
decodeBase64ForUIChatMessages(filterUIMessages); decodeBase64ForUIChatMessages(filterUIMessages);
@ -280,6 +279,7 @@ export const ChatGroup = ({
...decodedUIMessages, ...decodedUIMessages,
...response, ...response,
]; ];
const combineUIAndExtensionMsgs = processWithNewMessages( const combineUIAndExtensionMsgs = processWithNewMessages(
combineUIAndExtensionMsgsBefore.map((item) => ({ combineUIAndExtensionMsgsBefore.map((item) => ({
...item, ...item,
@ -287,16 +287,24 @@ export const ChatGroup = ({
})), })),
selectedGroup selectedGroup
); );
res(combineUIAndExtensionMsgs); res(combineUIAndExtensionMsgs);
if (isInitiated) { if (isInitiated) {
const formatted = combineUIAndExtensionMsgs const formatted = combineUIAndExtensionMsgs
.filter((rawItem) => !rawItem?.chatReference) .filter((rawItem) => !rawItem?.chatReference)
.map((item) => { .map((item) => {
const message = (
<p>
{t('group:message.generic.group_key_created', {
postProcess: 'capitalizeFirst',
})}
</p>
);
const additionalFields = 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 { return {
@ -362,7 +370,9 @@ export const ChatGroup = ({
!newTimestamp !newTimestamp
) { ) {
console.warn( console.warn(
'Invalid content, sender, or timestamp in reaction data', t('group:message.generic.invalid_content', {
postProcess: 'capitalizeFirst',
}),
item item
); );
return; return;
@ -435,10 +445,17 @@ export const ChatGroup = ({
const formatted = combineUIAndExtensionMsgs const formatted = combineUIAndExtensionMsgs
.filter((rawItem) => !rawItem?.chatReference) .filter((rawItem) => !rawItem?.chatReference)
.map((item) => { .map((item) => {
const message = (
<p>
{t('group:message.generic.group_key_created', {
postProcess: 'capitalizeFirst',
})}
</p>
);
const additionalFields = const additionalFields =
item?.data === 'NDAwMQ==' item?.data === 'NDAwMQ=='
? { ? {
text: '<p>First group key created.</p>', text: message,
} }
: {}; : {};
const divide = const divide =
@ -510,7 +527,9 @@ export const ChatGroup = ({
!newTimestamp !newTimestamp
) { ) {
console.warn( console.warn(
'Invalid content, sender, or timestamp in reaction data', t('group:message.generic.invalid_content', {
postProcess: 'capitalizeFirst',
}),
item item
); );
return; return;
@ -583,7 +602,12 @@ export const ChatGroup = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
@ -615,6 +639,7 @@ export const ChatGroup = ({
console.error('Error during ping:', error); console.error('Error during ping:', error);
} }
}; };
const initWebsocketMessageGroup = () => { const initWebsocketMessageGroup = () => {
let socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?txGroupId=${selectedGroup}&encoding=BASE64&limit=100`; let socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?txGroupId=${selectedGroup}&encoding=BASE64&limit=100`;
socketRef.current = new WebSocket(socketLink); socketRef.current = new WebSocket(socketLink);
@ -709,7 +734,12 @@ export const ChatGroup = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
@ -744,7 +774,12 @@ export const ChatGroup = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
@ -760,12 +795,22 @@ export const ChatGroup = ({
const sendMessage = async () => { const sendMessage = async () => {
try { try {
if (messageSize > 4000) return; if (messageSize > 4000) return; // TODO magic number
if (isPrivate === null) 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 (isSending) return;
if (+balance < 4) 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(); pauseAllQueues();
if (editorRef.current) { if (editorRef.current) {
const htmlContent = editorRef.current.getHTML(); const htmlContent = editorRef.current.getHTML();
@ -782,23 +827,36 @@ export const ChatGroup = ({
if (replyMessage?.chatReference) { if (replyMessage?.chatReference) {
repliedTo = replyMessage?.chatReference; repliedTo = replyMessage?.chatReference;
} }
let chatReference = onEditMessage?.signature;
const chatReference = onEditMessage?.signature;
const publicData = isPrivate const publicData = isPrivate
? {} ? {}
: { : {
isEdited: chatReference ? true : false, isEdited: chatReference ? true : false,
}; };
const imagesToPublish = [];
interface ImageToPublish {
service: string;
identifier: string;
name: string;
base64: string;
}
const imagesToPublish: ImageToPublish[] = [];
const deleteImage = const deleteImage =
onEditMessage && isDeleteImage && messageHasImage(onEditMessage); onEditMessage && isDeleteImage && messageHasImage(onEditMessage);
if (deleteImage) { if (deleteImage) {
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
message: 'Would you like to delete your previous chat image?', message: t('core:message.question.delete_chat_image', {
postProcess: 'capitalizeFirst',
}),
}); });
// TODO magic string
await window.sendMessage('publishOnQDN', { await window.sendMessage('publishOnQDN', {
data: 'RA==', data: 'RA==',
identifier: onEditMessage?.images[0]?.identifier, identifier: onEditMessage?.images[0]?.identifier,
@ -806,12 +864,14 @@ export const ChatGroup = ({
uploadType: 'base64', uploadType: 'base64',
}); });
} }
if (chatImagesToSave?.length > 0) { if (chatImagesToSave?.length > 0) {
const imageToSave = chatImagesToSave[0]; const imageToSave = chatImagesToSave[0];
const base64ToSave = isPrivate const base64ToSave = isPrivate
? await encryptChatMessage(imageToSave, secretKeyObject) ? await encryptChatMessage(imageToSave, secretKeyObject)
: imageToSave; : imageToSave;
// 1 represents public group, 0 is private // 1 represents public group, 0 is private
const identifier = `grp-q-manager_${isPrivate ? 0 : 1}_group_${selectedGroup}_${uidImages.rnd()}`; const identifier = `grp-q-manager_${isPrivate ? 0 : 1}_group_${selectedGroup}_${uidImages.rnd()}`;
imagesToPublish.push({ imagesToPublish.push({
@ -823,14 +883,18 @@ export const ChatGroup = ({
const res = await window.sendMessage( const res = await window.sendMessage(
'PUBLISH_MULTIPLE_QDN_RESOURCES', 'PUBLISH_MULTIPLE_QDN_RESOURCES',
{ {
resources: imagesToPublish, resources: imagesToPublish,
}, },
240000, 240000,
true true
); );
if (res !== true) throw new Error('Unable to publish images'); if (res !== true)
throw new Error(
t('core:message.error.unable_publish_image', {
postProcess: 'capitalizeFirst',
})
);
} }
const images = const images =
@ -869,7 +933,6 @@ export const ChatGroup = ({
isPrivate === false isPrivate === false
? JSON.stringify(objectMessage) ? JSON.stringify(objectMessage)
: await encryptChatMessage(message64, secretKeyObject); : await encryptChatMessage(message64, secretKeyObject);
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
const sendMessageFunc = async () => { const sendMessageFunc = async () => {
return await sendChatGroup({ return await sendChatGroup({
@ -980,18 +1043,25 @@ export const ChatGroup = ({
.setContent(message?.messageText || message?.text) .setContent(message?.messageText || message?.text)
.run(); .run();
}, []); }, []);
const handleReaction = useCallback( const handleReaction = useCallback(
async (reaction, chatMessage, reactionState = true) => { async (reaction, chatMessage, reactionState = true) => {
try { try {
if (isSending) return; if (isSending) return;
if (+balance < 4) if (+balance < 4)
throw new Error('You need at least 4 QORT to send a message'); // TODO magic number
pauseAllQueues(); throw new Error(
t('group:message.error.qortals_required', {
quantity: 4,
postProcess: 'capitalizeFirst',
})
);
pauseAllQueues();
setIsSending(true); setIsSending(true);
const message = ''; const message = '';
const secretKeyObject = await getSecretKey(false, true); const secretKeyObject = await getSecretKey(false, true);
const otherData = { const otherData = {
specialId: uid.rnd(), specialId: uid.rnd(),
type: 'reaction', type: 'reaction',
@ -1012,8 +1082,6 @@ export const ChatGroup = ({
secretKeyObject, secretKeyObject,
reactiontypeNumber reactiontypeNumber
); );
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
const sendMessageFunc = async () => { const sendMessageFunc = async () => {
return await sendChatGroup({ return await sendChatGroup({
groupId: selectedGroup, groupId: selectedGroup,
@ -1034,12 +1102,6 @@ export const ChatGroup = ({
chatReference: chatMessage.signature, chatReference: chatMessage.signature,
}; };
addToQueue(sendMessageFunc, messageObj, 'chat-reaction', selectedGroup); addToQueue(sendMessageFunc, messageObj, 'chat-reaction', selectedGroup);
// setTimeout(() => {
// executeEvent("sent-new-message-group", {})
// }, 150);
// clearEditorContent()
// setReplyMessage(null)
// send chat message // send chat message
} catch (error) { } catch (error) {
const errorMsg = error?.message || error; const errorMsg = error?.message || error;
@ -1071,7 +1133,9 @@ export const ChatGroup = ({
) { ) {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: 'This message already has an image', message: t('core:message.generic.message_with_image', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
return; return;
@ -1080,6 +1144,7 @@ export const ChatGroup = ({
}, },
[chatImagesToSave, onEditMessage?.images, isDeleteImage] [chatImagesToSave, onEditMessage?.images, isDeleteImage]
); );
return ( return (
<div <div
style={{ style={{
@ -1088,34 +1153,36 @@ export const ChatGroup = ({
height: '100%', height: '100%',
left: hide && '-100000px', left: hide && '-100000px',
opacity: hide ? 0 : 1, opacity: hide ? 0 : 1,
padding: '10px',
position: hide ? 'absolute' : 'relative', position: hide ? 'absolute' : 'relative',
width: '100%', width: '100%',
padding: '10px',
}} }}
> >
<ChatList <ChatList
isPrivate={isPrivate}
hasSecretKey={!!secretKey}
openQManager={openQManager}
enableMentions
onReply={onReply}
onEdit={onEdit}
chatId={selectedGroup} chatId={selectedGroup}
initialMessages={messages}
myAddress={myAddress}
tempMessages={tempMessages}
handleReaction={handleReaction}
chatReferences={chatReferences} chatReferences={chatReferences}
tempChatReferences={tempChatReferences} enableMentions
handleReaction={handleReaction}
hasSecretKey={!!secretKey}
initialMessages={messages}
isPrivate={isPrivate}
members={members} members={members}
myAddress={myAddress}
myName={myName} myName={myName}
onEdit={onEdit}
onReply={onReply}
openQManager={openQManager}
selectedGroup={selectedGroup} selectedGroup={selectedGroup}
tempChatReferences={tempChatReferences}
tempMessages={tempMessages}
/> />
{(!!secretKey || isPrivate === false) && ( {(!!secretKey || isPrivate === false) && (
<div <div
style={{ style={{
backgroundColor: theme.palette.background.surface, backgroundColor: theme.palette.background.surface,
border: `1px solid ${theme.palette.border.subtle}`,
borderRadius: '10px',
bottom: isFocusedParent ? '0px' : 'unset', bottom: isFocusedParent ? '0px' : 'unset',
boxSizing: 'border-box', boxSizing: 'border-box',
display: 'flex', display: 'flex',
@ -1128,8 +1195,6 @@ export const ChatGroup = ({
top: isFocusedParent ? '0px' : 'unset', top: isFocusedParent ? '0px' : 'unset',
width: '100%', width: '100%',
zIndex: isFocusedParent ? 5 : 'unset', zIndex: isFocusedParent ? 5 : 'unset',
border: `1px solid ${theme.palette.border.subtle}`,
borderRadius: '10px',
}} }}
> >
<div <div
@ -1147,9 +1212,9 @@ export const ChatGroup = ({
sx={{ sx={{
alignItems: 'flex-start', alignItems: 'flex-start',
display: 'flex', display: 'flex',
width: '100%',
gap: '10px',
flexWrap: 'wrap', flexWrap: 'wrap',
gap: '10px',
width: '100%',
}} }}
> >
{!isDeleteImage && {!isDeleteImage &&
@ -1159,19 +1224,20 @@ export const ChatGroup = ({
<div <div
key={index} key={index}
style={{ style={{
position: 'relative',
height: '50px', height: '50px',
position: 'relative',
width: '50px', width: '50px',
}} }}
> >
<ImageIcon <ImageIcon
color="primary" color="primary"
sx={{ sx={{
borderRadius: '3px',
height: '100%', height: '100%',
width: '100%', width: '100%',
borderRadius: '3px',
}} }}
/> />
<Tooltip title="Delete image"> <Tooltip title="Delete image">
<IconButton <IconButton
onClick={() => setIsDeleteImage(true)} onClick={() => setIsDeleteImage(true)}
@ -1201,12 +1267,13 @@ export const ChatGroup = ({
</Tooltip> </Tooltip>
</div> </div>
))} ))}
{chatImagesToSave.map((imgBase64, index) => ( {chatImagesToSave.map((imgBase64, index) => (
<div <div
key={index} key={index}
style={{ style={{
position: 'relative',
height: '50px', height: '50px',
position: 'relative',
width: '50px', width: '50px',
}} }}
> >
@ -1219,6 +1286,7 @@ export const ChatGroup = ({
borderRadius: '3px', borderRadius: '3px',
}} }}
/> />
<Tooltip title="Remove image"> <Tooltip title="Remove image">
<IconButton <IconButton
onClick={() => onClick={() =>
@ -1253,6 +1321,7 @@ export const ChatGroup = ({
</div> </div>
))} ))}
</Box> </Box>
{replyMessage && ( {replyMessage && (
<Box <Box
sx={{ sx={{
@ -1277,6 +1346,7 @@ export const ChatGroup = ({
</ButtonBase> </ButtonBase>
</Box> </Box>
)} )}
{onEditMessage && ( {onEditMessage && (
<Box <Box
sx={{ sx={{
@ -1322,13 +1392,19 @@ export const ChatGroup = ({
width: '100%', width: '100%',
}} }}
> >
<Typography <Typography //TODO magic number
sx={{ sx={{
fontSize: '12px', fontSize: '12px',
color: color:
messageSize > 4000 ? theme.palette.other.danger : 'unset', messageSize > 4000 ? theme.palette.other.danger : 'unset',
}} }}
>{`Your message size is of ${messageSize} bytes out of a maximum of 4000`}</Typography> >
{t('core:message.error.message_size', {
maximum: 4000,
size: messageSize,
postProcess: 'capitalizeFirst',
})}
</Typography>
</Box> </Box>
)} )}
</div> </div>
@ -1379,6 +1455,7 @@ export const ChatGroup = ({
</Box> </Box>
</div> </div>
)} )}
{isOpenQManager !== null && ( {isOpenQManager !== null && (
<Box <Box
sx={{ sx={{
@ -1418,6 +1495,7 @@ export const ChatGroup = ({
}} }}
> >
<Typography>Q-Manager</Typography> <Typography>Q-Manager</Typography>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setIsOpenQManager(false); setIsOpenQManager(false);
@ -1430,14 +1508,16 @@ export const ChatGroup = ({
/> />
</ButtonBase> </ButtonBase>
</Box> </Box>
<Divider /> <Divider />
<AppViewerContainer <AppViewerContainer
customHeight="560px" customHeight="560px"
app={{ app={{
tabId: '5558588',
name: 'Q-Manager', name: 'Q-Manager',
service: 'APP',
path: `?groupId=${selectedGroup}`, path: `?groupId=${selectedGroup}`,
service: 'APP',
tabId: '5558588',
}} }}
isSelected isSelected
/> />
@ -1448,7 +1528,7 @@ export const ChatGroup = ({
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{
message: 'Loading chat... please wait.', message: t('core:loading.chat', { postProcess: 'capitalizeFirst' }),
}} }}
/> />

View File

@ -5,6 +5,7 @@ import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import { Box, Button, Typography, useTheme } from '@mui/material'; import { Box, Button, Typography, useTheme } from '@mui/material';
import { ChatOptions } from './ChatOptions'; import { ChatOptions } from './ChatOptions';
import ErrorBoundary from '../../common/ErrorBoundary'; import ErrorBoundary from '../../common/ErrorBoundary';
import { useTranslation } from 'react-i18next';
export const ChatList = ({ export const ChatList = ({
initialMessages, initialMessages,
@ -180,6 +181,7 @@ export const ChatList = ({
}, []); }, []);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
return ( return (
<Box <Box
@ -202,11 +204,11 @@ export const ChatList = ({
ref={parentRef} ref={parentRef}
className="List" className="List"
style={{ style={{
display: 'flex',
flexGrow: 1, flexGrow: 1,
height: '0px',
overflow: 'auto', overflow: 'auto',
position: 'relative', position: 'relative',
display: 'flex',
height: '0px',
}} }}
> >
<div <div
@ -324,19 +326,23 @@ export const ChatList = ({
<div <div
key={virtualRow.index} key={virtualRow.index}
style={{ style={{
position: 'absolute',
top: 0,
left: '50%',
transform: `translateY(${virtualRow.start}px) translateX(-50%)`,
width: '100%',
padding: '10px 0',
display: 'flex',
alignItems: 'center', alignItems: 'center',
display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '5px', gap: '5px',
left: '50%',
padding: '10px 0',
position: 'absolute',
top: 0,
transform: `translateY(${virtualRow.start}px) translateX(-50%)`,
width: '100%',
}} }}
> >
<Typography>Error loading message.</Typography> <Typography>
{t('core:message.error.message_loading', {
postProcess: 'capitalizeFirst',
})}
</Typography>
</div> </div>
); );
} }
@ -363,26 +369,28 @@ export const ChatList = ({
<ErrorBoundary <ErrorBoundary
fallback={ fallback={
<Typography> <Typography>
Error loading content: Invalid Data {t('group:message.generic.invalid_data', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
} }
> >
<MessageItem <MessageItem
handleReaction={handleReaction}
isLast={index === messages.length - 1} isLast={index === messages.length - 1}
isPrivate={isPrivate}
isTemp={!!message?.isTemp}
isUpdating={isUpdating}
lastSignature={lastSignature} lastSignature={lastSignature}
message={message} message={message}
onSeen={handleMessageSeen}
isTemp={!!message?.isTemp}
myAddress={myAddress} myAddress={myAddress}
onReply={onReply}
onEdit={onEdit} onEdit={onEdit}
onReply={onReply}
onSeen={handleMessageSeen}
reactions={reactions}
reply={reply} reply={reply}
replyIndex={replyIndex} replyIndex={replyIndex}
scrollToItem={goToMessage} scrollToItem={goToMessage}
handleReaction={handleReaction}
reactions={reactions}
isUpdating={isUpdating}
isPrivate={isPrivate}
/> />
</ErrorBoundary> </ErrorBoundary>
</div> </div>
@ -391,6 +399,7 @@ export const ChatList = ({
</div> </div>
</div> </div>
</div> </div>
{showScrollButton && ( {showScrollButton && (
<button <button
onClick={() => scrollToBottom()} onClick={() => scrollToBottom()}
@ -408,9 +417,12 @@ export const ChatList = ({
zIndex: 10, zIndex: 10,
}} }}
> >
Scroll to Unread Messages {t('group:action.scroll_unread_messages', {
postProcess: 'capitalizeFirst',
})}
</button> </button>
)} )}
{showScrollDownButton && !showScrollButton && ( {showScrollDownButton && !showScrollButton && (
<Button <Button
onClick={() => scrollToBottom()} onClick={() => scrollToBottom()}
@ -431,10 +443,13 @@ export const ChatList = ({
textTransform: 'none', textTransform: 'none',
}} }}
> >
Scroll to bottom {t('group:action.scroll_unread_messages', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
)} )}
</div> </div>
{enableMentions && (hasSecretKey || isPrivate === false) && ( {enableMentions && (hasSecretKey || isPrivate === false) && (
<ChatOptions <ChatOptions
openQManager={openQManager} openQManager={openQManager}

View File

@ -24,7 +24,6 @@ import {
AppsSearchLeft, AppsSearchLeft,
AppsSearchRight, AppsSearchRight,
} from '../Apps/Apps-styles'; } from '../Apps/Apps-styles';
import IconClearInput from '../../assets/svgs/ClearInput.svg'; import IconClearInput from '../../assets/svgs/ClearInput.svg';
import { CellMeasurerCache } from 'react-virtualized'; import { CellMeasurerCache } from 'react-virtualized';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
@ -35,6 +34,7 @@ import { ContextMenuMentions } from '../ContextMenuMentions';
import { convert } from 'html-to-text'; import { convert } from 'html-to-text';
import { generateHTML } from '@tiptap/react'; import { generateHTML } from '@tiptap/react';
import ErrorBoundary from '../../common/ErrorBoundary'; import ErrorBoundary from '../../common/ErrorBoundary';
import { useTranslation } from 'react-i18next';
const extractTextFromHTML = (htmlString = '') => { const extractTextFromHTML = (htmlString = '') => {
return convert(htmlString, { return convert(htmlString, {
@ -60,10 +60,12 @@ export const ChatOptions = ({
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [selectedMember, setSelectedMember] = useState(0); const [selectedMember, setSelectedMember] = useState(0);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const parentRef = useRef(null); const parentRef = useRef(null);
const parentRefMentions = useRef(null); const parentRefMentions = useRef(null);
const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null); const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null);
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
const messages = useMemo(() => { const messages = useMemo(() => {
return untransformedMessages?.map((item) => { return untransformedMessages?.map((item) => {
if (item?.messageText) { if (item?.messageText) {
@ -80,7 +82,7 @@ export const ChatOptions = ({
messageText: transformedMessage, messageText: transformedMessage,
}; };
} catch (error) { } catch (error) {
// error console.log(error);
} }
} else return item; } else return item;
}); });
@ -102,7 +104,12 @@ export const ChatOptions = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
@ -154,6 +161,7 @@ export const ChatOptions = ({
} }
return []; return [];
} }
if (selectedMember) { if (selectedMember) {
return messages return messages
.filter( .filter(
@ -165,6 +173,7 @@ export const ChatOptions = ({
) )
?.sort((a, b) => b?.timestamp - a?.timestamp); ?.sort((a, b) => b?.timestamp - a?.timestamp);
} }
return messages return messages
.filter((message) => .filter((message) =>
extractTextFromHTML( extractTextFromHTML(
@ -187,6 +196,7 @@ export const ChatOptions = ({
) )
?.sort((a, b) => b?.timestamp - a?.timestamp); ?.sort((a, b) => b?.timestamp - a?.timestamp);
} }
return messages return messages
.filter((message) => .filter((message) =>
extractTextFromHTML(message?.decryptedData?.message)?.includes( extractTextFromHTML(message?.decryptedData?.message)?.includes(
@ -251,6 +261,7 @@ export const ChatOptions = ({
}} }}
/> />
</Box> </Box>
<Box <Box
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@ -268,9 +279,12 @@ export const ChatOptions = ({
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
> >
No results {t('core:message.generic.no_results', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -387,6 +401,7 @@ export const ChatOptions = ({
}} }}
/> />
</Box> </Box>
<Box <Box
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@ -415,6 +430,7 @@ export const ChatOptions = ({
}} }}
/> />
</AppsSearchLeft> </AppsSearchLeft>
<AppsSearchRight> <AppsSearchRight>
{searchValue && ( {searchValue && (
<ButtonBase <ButtonBase
@ -427,6 +443,7 @@ export const ChatOptions = ({
)} )}
</AppsSearchRight> </AppsSearchRight>
</AppsSearchContainer> </AppsSearchContainer>
<Box <Box
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@ -444,8 +461,11 @@ export const ChatOptions = ({
value={selectedMember} value={selectedMember}
> >
<MenuItem value={0}> <MenuItem value={0}>
<em>By member</em> <em>
{t('core:sort.by_member', { postProcess: 'capitalizeFirst' })}
</em>
</MenuItem> </MenuItem>
{members?.map((member) => { {members?.map((member) => {
return ( return (
<MenuItem key={member} value={member}> <MenuItem key={member} value={member}>
@ -454,6 +474,7 @@ export const ChatOptions = ({
); );
})} })}
</Select> </Select>
{!!selectedMember && ( {!!selectedMember && (
<CloseIcon <CloseIcon
onClick={() => { onClick={() => {
@ -475,9 +496,12 @@ export const ChatOptions = ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}} }}
> >
No results {t('core:message.generic.no_results', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -544,7 +568,9 @@ export const ChatOptions = ({
<ErrorBoundary <ErrorBoundary
fallback={ fallback={
<Typography> <Typography>
Error loading content: Invalid Data {t('group:message.generic.invalid_data', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
} }
> >
@ -566,6 +592,7 @@ export const ChatOptions = ({
</Box> </Box>
); );
} }
return ( return (
<Box <Box
sx={{ sx={{
@ -605,7 +632,7 @@ export const ChatOptions = ({
fontWeight: 700, fontWeight: 700,
}} }}
> >
SEARCH {t('core:action.search', { postProcess: 'capitalizeAll' })}
</span> </span>
} }
placement="left" placement="left"
@ -628,6 +655,7 @@ export const ChatOptions = ({
<SearchIcon /> <SearchIcon />
</Tooltip> </Tooltip>
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setMode('default'); setMode('default');
@ -645,7 +673,7 @@ export const ChatOptions = ({
fontWeight: 700, fontWeight: 700,
}} }}
> >
Q-MANAGER {t('core:q_apps.q_manager', { postProcess: 'capitalizeAll' })}
</span> </span>
} }
placement="left" placement="left"
@ -668,6 +696,7 @@ export const ChatOptions = ({
<InsertLinkIcon sx={{ color: theme.palette.text.primary }} /> <InsertLinkIcon sx={{ color: theme.palette.text.primary }} />
</Tooltip> </Tooltip>
</ButtonBase> </ButtonBase>
<ContextMenuMentions <ContextMenuMentions
getTimestampMention={getTimestampMention} getTimestampMention={getTimestampMention}
groupId={selectedGroup} groupId={selectedGroup}
@ -688,7 +717,9 @@ export const ChatOptions = ({
fontWeight: 700, fontWeight: 700,
}} }}
> >
MENTIONED {t('core:message.generic.mentioned', {
postProcess: 'capitalizeAll',
})}
</span> </span>
} }
placement="left" placement="left"
@ -767,6 +798,7 @@ const ShowMessage = ({ message, goToMessage, messages }) => {
> >
{message?.senderName?.charAt(0)} {message?.senderName?.charAt(0)}
</Avatar> </Avatar>
<Typography <Typography
sx={{ sx={{
fontWight: 600, fontWight: 600,
@ -787,6 +819,7 @@ const ShowMessage = ({ message, goToMessage, messages }) => {
> >
{formatTimestamp(message.timestamp)} {formatTimestamp(message.timestamp)}
</Typography> </Typography>
<Box <Box
style={{ style={{
cursor: 'pointer', cursor: 'pointer',

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 { Box, Button, Typography, useTheme } from '@mui/material';
import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
@ -18,6 +18,7 @@ import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { txListAtom } from '../../atoms/global'; import { txListAtom } from '../../atoms/global';
import { useTranslation } from 'react-i18next';
export const CreateCommonSecret = ({ export const CreateCommonSecret = ({
groupId, groupId,
@ -34,14 +35,14 @@ export const CreateCommonSecret = ({
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
const setTxList = useSetAtom(txListAtom); const setTxList = useSetAtom(txListAtom);
const [openSnack, setOpenSnack] = React.useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = React.useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [isLoading, setIsLoading] = React.useState(false); const [isLoading, setIsLoading] = useState(false);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const getPublishesFromAdmins = async (admins: string[]) => { const getPublishesFromAdmins = async (admins: string[]) => {
// const validApi = await findUsableApi();
const queryString = admins.map((name) => `name=${name}`).join('&'); const queryString = admins.map((name) => `name=${name}`).join('&');
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${
groupId groupId
@ -55,9 +56,11 @@ export const CreateCommonSecret = ({
const filterId = adminData.filter( const filterId = adminData.filter(
(data: any) => data.identifier === `symmetric-qchat-group-${groupId}` (data: any) => data.identifier === `symmetric-qchat-group-${groupId}`
); );
if (filterId?.length === 0) { if (filterId?.length === 0) {
return false; return false;
} }
const sortedData = filterId.sort((a: any, b: any) => { const sortedData = filterId.sort((a: any, b: any) => {
// Get the most recent date for both a and b // Get the most recent date for both a and b
const dateA = a.updated ? new Date(a.updated) : new Date(a.created); const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
@ -78,8 +81,13 @@ export const CreateCommonSecret = ({
pauseAllQueues(); pauseAllQueues();
const { names } = await getGroupAdmins(groupId); const { names } = await getGroupAdmins(groupId);
if (!names.length) { if (!names.length) {
throw new Error('Network error'); throw new Error(
t('core:message.error.network_generic', {
postProcess: 'capitalizeFirst',
})
);
} }
const publish = await getPublishesFromAdmins(names); const publish = await getPublishesFromAdmins(names);
@ -92,15 +100,18 @@ export const CreateCommonSecret = ({
publish.identifier publish.identifier
}?encoding=base64&rebuild=true` }?encoding=base64&rebuild=true`
); );
const data = await res.text(); const data = await res.text();
const decryptedKey: any = await decryptResource(data); const decryptedKey: any = await decryptResource(data);
const dataint8Array = base64ToUint8Array(decryptedKey.data); const dataint8Array = base64ToUint8Array(decryptedKey.data);
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
if (!validateSecretKey(decryptedKeyToObject)) if (!validateSecretKey(decryptedKeyToObject))
throw new Error('SecretKey is not valid'); throw new Error(
t('auth:message.error.invalid_secret_key', {
postProcess: 'capitalizeFirst',
})
);
if (decryptedKeyToObject) { if (decryptedKeyToObject) {
return decryptedKeyToObject; return decryptedKeyToObject;
@ -113,17 +124,31 @@ export const CreateCommonSecret = ({
const createCommonSecret = async () => { const createCommonSecret = async () => {
try { try {
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
message: 'Would you like to perform an ARBITRARY transaction?', message: t('core:message.question.perform_transaction', {
action: 'ARBITRARY',
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoading(true); setIsLoading(true);
const secretKey2 = await getSecretKey(); const secretKey2 = await getSecretKey();
if (!secretKey2 && secretKey2 !== false) if (!secretKey2 && secretKey2 !== false)
throw new Error('invalid secret key'); throw new Error(
t('auth:message.error.invalid_secret_key', {
postProcess: 'capitalizeFirst',
})
);
if (secretKey2 && !validateSecretKey(secretKey2)) if (secretKey2 && !validateSecretKey(secretKey2))
throw new Error('invalid secret key'); throw new Error(
t('auth:message.error.invalid_secret_key', {
postProcess: 'capitalizeFirst',
})
);
const secretKeyToSend = !secretKey2 ? null : secretKey2; const secretKeyToSend = !secretKey2 ? null : secretKey2;
@ -136,16 +161,26 @@ export const CreateCommonSecret = ({
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('auth:message.success.reencrypted_secret_key', {
'Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.', postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
setTxList((prev) => [ setTxList((prev) => [
{ {
...response, ...response,
type: 'created-common-secret', type: 'created-common-secret',
label: `Published secret key for group ${groupId}: awaiting confirmation`, label: t('group:message.success.published_secret_key', {
labelDone: `Published secret key for group ${groupId}: success!`, group_id: groupId,
postProcess: 'capitalizeFirst',
}),
labelDone: t(
'group:message.success.published_secret_key_label',
{
group_id: groupId,
postProcess: 'capitalizeFirst',
}
),
done: false, done: false,
groupId, groupId,
}, },
@ -187,13 +222,15 @@ export const CreateCommonSecret = ({
variant="contained" variant="contained"
onClick={createCommonSecret} onClick={createCommonSecret}
> >
Re-encrypt key {t('auth:action.reencrypt_key', { postProcess: 'capitalizeFirst' })}
</LoadingButton> </LoadingButton>
{noSecretKey ? ( {noSecretKey ? (
<Box> <Box>
<Typography> <Typography>
There is no group secret key. Be the first admin to publish one! {t('group:message.generic.group_no_secret_key', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
) : isOwner && ) : isOwner &&
@ -202,14 +239,17 @@ export const CreateCommonSecret = ({
userInfo.name !== secretKeyDetails?.name ? ( userInfo.name !== secretKeyDetails?.name ? (
<Box> <Box>
<Typography> <Typography>
The latest group secret key was published by a non-owner. As the {t('group:message.generic.group_secret_key_no_owner', {
owner of the group please re-encrypt the key as a safeguard postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
) : isForceShowCreationKeyPopup ? null : ( ) : isForceShowCreationKeyPopup ? null : (
<Box> <Box>
<Typography> <Typography>
The group member list has changed. Please re-encrypt the secret key. {t('group:message.generic.group_member_list_changed', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
@ -228,7 +268,7 @@ export const CreateCommonSecret = ({
}} }}
size="small" size="small"
> >
Hide {t('core:action.hide', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</Box> </Box>

View File

@ -1,7 +1,9 @@
import React, { import {
useCallback, useCallback,
useContext,
useEffect, useEffect,
useMemo, useMemo,
useReducer,
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
@ -140,9 +142,9 @@ export const GroupAnnouncements = ({
const [selectedAnnouncement, setSelectedAnnouncement] = useState(null); const [selectedAnnouncement, setSelectedAnnouncement] = useState(null);
const [isFocusedParent, setIsFocusedParent] = useState(false); const [isFocusedParent, setIsFocusedParent] = useState(false);
const { show } = React.useContext(MyContext); const { show } = useContext(MyContext);
const [openSnack, setOpenSnack] = React.useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = React.useState(null); const [infoSnack, setInfoSnack] = useState(null);
const hasInitialized = useRef(false); const hasInitialized = useRef(false);
const hasInitializedWebsocket = useRef(false); const hasInitializedWebsocket = useRef(false);
const editorRef = useRef(null); const editorRef = useRef(null);
@ -150,12 +152,13 @@ export const GroupAnnouncements = ({
const setEditorRef = (editorInstance) => { const setEditorRef = (editorInstance) => {
editorRef.current = editorInstance; editorRef.current = editorInstance;
}; };
const [, forceUpdate] = React.useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
const { t } = useTranslation(['core', 'group']); const { t } = useTranslation(['auth', 'core', 'group']);
const triggerRerender = () => { const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state forceUpdate(); // Trigger re-render by updating the state
}; };
useEffect(() => { useEffect(() => {
if (!selectedGroup) return; if (!selectedGroup) return;
(async () => { (async () => {
@ -231,7 +234,12 @@ export const GroupAnnouncements = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
} catch (error) { } catch (error) {
@ -254,7 +262,12 @@ export const GroupAnnouncements = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
}; };
@ -289,9 +302,9 @@ export const GroupAnnouncements = ({
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'ARBITRARY', action: 'ARBITRARY',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -334,7 +347,7 @@ export const GroupAnnouncements = ({
setTempData(selectedGroup); setTempData(selectedGroup);
clearEditorContent(); clearEditorContent();
} }
// TODO send chat message // send chat message
} catch (error) { } catch (error) {
if (!error) return; if (!error) return;
setInfoSnack({ setInfoSnack({
@ -348,7 +361,7 @@ export const GroupAnnouncements = ({
} }
}; };
const getAnnouncements = React.useCallback( const getAnnouncements = useCallback(
async (selectedGroup, isPrivate) => { async (selectedGroup, isPrivate) => {
try { try {
const offset = 0; const offset = 0;
@ -384,7 +397,7 @@ export const GroupAnnouncements = ({
[secretKey] [secretKey]
); );
React.useEffect(() => { useEffect(() => {
if (!secretKey && isPrivate) return; if (!secretKey && isPrivate) return;
if ( if (
selectedGroup && selectedGroup &&
@ -429,7 +442,7 @@ export const GroupAnnouncements = ({
const theme = useTheme(); const theme = useTheme();
const checkNewMessages = React.useCallback(async () => { const checkNewMessages = useCallback(async () => {
try { try {
const identifier = `grp-${selectedGroup}-anc-`; const identifier = `grp-${selectedGroup}-anc-`;
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`; const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=20&includemetadata=false&offset=${0}&reverse=true&prefix=true`;
@ -583,7 +596,9 @@ export const GroupAnnouncements = ({
fontSize: '30px', fontSize: '30px',
}} }}
/> />
Group Announcements {t('group:message.generic.group_announcement', {
postProcess: 'capitalizeFirst',
})}
</Box> </Box>
<Spacer height={'25px'} /> <Spacer height={'25px'} />
@ -602,10 +617,13 @@ export const GroupAnnouncements = ({
fontSize: '16px', fontSize: '16px',
}} }}
> >
No announcements {t('group:message.generic.no_announcement', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
<AnnouncementList <AnnouncementList
announcementData={announcementData} announcementData={announcementData}
initialMessages={combinedListTempAndReal} initialMessages={combinedListTempAndReal}
@ -686,7 +704,7 @@ export const GroupAnnouncements = ({
padding: '5px', padding: '5px',
}} }}
> >
{` Close`} {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</CustomButton> </CustomButton>
)} )}
@ -720,7 +738,9 @@ export const GroupAnnouncements = ({
}} }}
/> />
)} )}
{` Publish Announcement`} {t('group:action.publish_announcement', {
postProcess: 'capitalizeFirst',
})}
</CustomButton> </CustomButton>
</Box> </Box>
</div> </div>
@ -736,7 +756,9 @@ export const GroupAnnouncements = ({
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{
message: 'Loading announcements... please wait.', message: t('core:loading.announcements', {
postProcess: 'capitalizeFirst',
}),
}} }}
/> />
</div> </div>

View File

@ -1,6 +1,10 @@
import { useCallback, useContext, useEffect, useState } from 'react'; import { useCallback, useContext, useEffect, useState } from 'react';
import Logo2 from '../assets/svgs/Logo2.svg'; import Logo2 from '../../assets/svgs/Logo2.svg';
import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App'; import {
MyContext,
getArbitraryEndpointReact,
getBaseApiReact,
} from '../../App';
import { import {
Avatar, Avatar,
Box, Box,
@ -10,12 +14,13 @@ import {
Typography, Typography,
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import { Spacer } from '../common/Spacer'; import { Spacer } from '../../common/Spacer';
import ImageUploader from '../common/ImageUploader'; import ImageUploader from '../../common/ImageUploader';
import { getFee } from '../background'; import { getFee } from '../../background';
import { fileToBase64 } from '../utils/fileReading'; import { fileToBase64 } from '../../utils/fileReading';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import ErrorIcon from '@mui/icons-material/Error'; import ErrorIcon from '@mui/icons-material/Error';
import { useTranslation } from 'react-i18next';
export const GroupAvatar = ({ export const GroupAvatar = ({
myName, myName,
@ -28,7 +33,7 @@ export const GroupAvatar = ({
const [avatarFile, setAvatarFile] = useState(null); const [avatarFile, setAvatarFile] = useState(null);
const [tempAvatar, setTempAvatar] = useState(null); const [tempAvatar, setTempAvatar] = useState(null);
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
const { t } = useTranslation(['auth', 'core', 'group']);
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
// Handle child element click to open Popover // Handle child element click to open Popover
@ -64,6 +69,7 @@ export const GroupAvatar = ({
console.log(error); console.log(error);
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!myName || !groupId) return; if (!myName || !groupId) return;
checkIfAvatarExists(myName, groupId); checkIfAvatarExists(myName, groupId);
@ -73,14 +79,24 @@ export const GroupAvatar = ({
try { try {
if (!groupId) return; if (!groupId) return;
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
if (+balance < +fee.fee) if (+balance < +fee.fee)
throw new Error(`Publishing an Avatar requires ${fee.fee}`); throw new Error(
t('core:message.generic.avatar_publish_fee', {
fee: fee.fee,
postProcess: 'capitalizeFirst',
})
);
await show({ await show({
message: 'Would you like to publish an avatar?', message: t('core:message.question.publish_avatar', {
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoading(true); setIsLoading(true);
const avatarBase64 = await fileToBase64(avatarFile); const avatarBase64 = await fileToBase64(avatarFile);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage('publishOnQDN', { .sendMessage('publishOnQDN', {
@ -97,7 +113,12 @@ export const GroupAvatar = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
);
}); });
}); });
setAvatarFile(null); setAvatarFile(null);
@ -129,6 +150,7 @@ export const GroupAvatar = ({
> >
{myName?.charAt(0)} {myName?.charAt(0)}
</Avatar> </Avatar>
<ButtonBase onClick={handleChildClick}> <ButtonBase onClick={handleChildClick}>
<Typography <Typography
sx={{ sx={{
@ -136,9 +158,10 @@ export const GroupAvatar = ({
opacity: 0.5, opacity: 0.5,
}} }}
> >
change avatar {t('core:action.change_avatar', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
<PopoverComp <PopoverComp
myName={myName} myName={myName}
avatarFile={avatarFile} avatarFile={avatarFile}
@ -167,6 +190,7 @@ export const GroupAvatar = ({
> >
{myName?.charAt(0)} {myName?.charAt(0)}
</Avatar> </Avatar>
<ButtonBase onClick={handleChildClick}> <ButtonBase onClick={handleChildClick}>
<Typography <Typography
sx={{ sx={{
@ -174,9 +198,10 @@ export const GroupAvatar = ({
opacity: 0.5, opacity: 0.5,
}} }}
> >
change avatar {t('core:action.change_avatar', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
<PopoverComp <PopoverComp
myName={myName} myName={myName}
avatarFile={avatarFile} avatarFile={avatarFile}
@ -202,9 +227,10 @@ export const GroupAvatar = ({
opacity: 0.5, opacity: 0.5,
}} }}
> >
set avatar {t('core:action.set_avatar', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
<PopoverComp <PopoverComp
myName={myName} myName={myName}
avatarFile={avatarFile} avatarFile={avatarFile}
@ -232,6 +258,8 @@ const PopoverComp = ({
myName, myName,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
return ( return (
<Popover <Popover
id={id} id={id}
@ -253,19 +281,28 @@ const PopoverComp = ({
fontSize: '12px', fontSize: '12px',
}} }}
> >
(500 KB max. for GIFS){' '} {t('core:message.generic.avatar_size', {
size: 500, // TODO magic number
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
<ImageUploader onPick={(file) => setAvatarFile(file)}> <ImageUploader onPick={(file) => setAvatarFile(file)}>
<Button variant="contained">Choose Image</Button> <Button variant="contained">
{t('core:action.choose_image', { postProcess: 'capitalizeFirst' })}
</Button>
</ImageUploader> </ImageUploader>
{avatarFile?.name} {avatarFile?.name}
<Spacer height="25px" /> <Spacer height="25px" />
{!myName && ( {!myName && (
<Box <Box
sx={{ sx={{
alignItems: 'center',
display: 'flex', display: 'flex',
gap: '5px', gap: '5px',
alignItems: 'center',
}} }}
> >
<ErrorIcon <ErrorIcon
@ -274,19 +311,22 @@ const PopoverComp = ({
}} }}
/> />
<Typography> <Typography>
A registered name is required to set an avatar {t('core:message.generic.avatar_registered_name', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
<Spacer height="25px" /> <Spacer height="25px" />
<LoadingButton <LoadingButton
loading={isLoading} loading={isLoading}
disabled={!avatarFile || !myName} disabled={!avatarFile || !myName}
onClick={publishAvatar} onClick={publishAvatar}
variant="contained" variant="contained"
> >
Publish avatar {t('group:action.publish_avatar', { postProcess: 'capitalizeFirst' })}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>

View File

@ -1,6 +1,9 @@
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { useTranslation } from 'react-i18next';
export default forwardRef((props, ref) => { export default forwardRef((props, ref) => {
const { t } = useTranslation(['auth', 'core', 'group']);
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
const selectItem = (index) => { const selectItem = (index) => {
@ -61,7 +64,11 @@ export default forwardRef((props, ref) => {
</button> </button>
)) ))
) : ( ) : (
<div className="item">No result</div> <div className="item">
{t('core:message.generic.no_results', {
postProcess: 'capitalizeFirst',
})}
</div>
)} )}
</div> </div>
); );

View File

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

View File

@ -1,4 +1,5 @@
import React, { import {
memo,
useCallback, useCallback,
useContext, useContext,
useEffect, useEffect,
@ -47,6 +48,7 @@ import level9Img from '../../assets/badges/level-9.png';
import level10Img from '../../assets/badges/level-10.png'; import level10Img from '../../assets/badges/level-10.png';
import { Embed } from '../Embeds/Embed'; import { Embed } from '../Embeds/Embed';
import { buildImageEmbedLink, messageHasImage } from '../../utils/chat'; import { buildImageEmbedLink, messageHasImage } from '../../utils/chat';
import { useTranslation } from 'react-i18next';
const getBadgeImg = (level) => { const getBadgeImg = (level) => {
switch (level?.toString()) { switch (level?.toString()) {
@ -77,7 +79,7 @@ const getBadgeImg = (level) => {
} }
}; };
const UserBadge = React.memo(({ userInfo }) => { const UserBadge = memo(({ userInfo }) => {
return ( return (
<Tooltip disableFocusListener title={`level ${userInfo ?? 0}`}> <Tooltip disableFocusListener title={`level ${userInfo ?? 0}`}>
<img <img
@ -92,7 +94,7 @@ const UserBadge = React.memo(({ userInfo }) => {
); );
}); });
export const MessageItem = React.memo( export const MessageItem = memo(
({ ({
message, message,
onSeen, onSeen,
@ -168,12 +170,15 @@ export const MessageItem = React.memo(
}, [message?.id]); }, [message?.id]);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
return ( return (
<> <>
{message?.divide && ( {message?.divide && (
<div className="unread-divider" id="unread-divider-id"> <div className="unread-divider" id="unread-divider-id">
Unread messages below {t('core:message.generic.unread_messages', {
postProcess: 'capitalizeFirst',
})}
</div> </div>
)} )}
@ -261,6 +266,7 @@ export const MessageItem = React.memo(
{message?.senderName || message?.sender} {message?.senderName || message?.sender}
</Typography> </Typography>
</WrapperUserAction> </WrapperUserAction>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -278,6 +284,7 @@ export const MessageItem = React.memo(
<EditIcon /> <EditIcon />
</ButtonBase> </ButtonBase>
)} )}
{!isShowingAsReply && ( {!isShowingAsReply && (
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
@ -287,6 +294,7 @@ export const MessageItem = React.memo(
<ReplyIcon /> <ReplyIcon />
</ButtonBase> </ButtonBase>
)} )}
{!isShowingAsReply && handleReaction && ( {!isShowingAsReply && handleReaction && (
<ReactionPicker <ReactionPicker
onReaction={(val) => { onReaction={(val) => {
@ -306,6 +314,7 @@ export const MessageItem = React.memo(
)} )}
</Box> </Box>
</Box> </Box>
{reply && ( {reply && (
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
@ -332,6 +341,7 @@ export const MessageItem = React.memo(
flexShrink: 0, flexShrink: 0,
}} }}
/> />
<Box <Box
sx={{ sx={{
padding: '5px', padding: '5px',
@ -343,11 +353,16 @@ export const MessageItem = React.memo(
fontWeight: 600, fontWeight: 600,
}} }}
> >
Replied to {reply?.senderName || reply?.senderAddress} {t('core:message.generic.replied_to', {
person: reply?.senderName || reply?.senderAddress,
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
{reply?.messageText && ( {reply?.messageText && (
<MessageDisplay htmlContent={htmlReply} /> <MessageDisplay htmlContent={htmlReply} />
)} )}
{reply?.decryptedData?.type === 'notification' ? ( {reply?.decryptedData?.type === 'notification' ? (
<MessageDisplay <MessageDisplay
htmlContent={reply.decryptedData?.data?.message} htmlContent={reply.decryptedData?.data?.message}
@ -359,6 +374,7 @@ export const MessageItem = React.memo(
</Box> </Box>
</> </>
)} )}
{htmlText && <MessageDisplay htmlContent={htmlText} />} {htmlText && <MessageDisplay htmlContent={htmlText} />}
{message?.decryptedData?.type === 'notification' ? ( {message?.decryptedData?.type === 'notification' ? (
@ -453,14 +469,17 @@ export const MessageItem = React.memo(
> >
<Box sx={{ p: 2 }}> <Box sx={{ p: 2 }}>
<Typography variant="subtitle1" sx={{ marginBottom: 1 }}> <Typography variant="subtitle1" sx={{ marginBottom: 1 }}>
People who reacted with {selectedReaction} {t('core:message.generic.people_reaction', {
reaction: selectedReaction,
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
<List <List
sx={{ sx={{
overflow: 'auto',
maxWidth: '300px',
maxHeight: '300px', maxHeight: '300px',
maxWidth: '300px',
overflow: 'auto',
}} }}
> >
{reactions[selectedReaction]?.map((reactionItem) => ( {reactions[selectedReaction]?.map((reactionItem) => (
@ -473,6 +492,7 @@ export const MessageItem = React.memo(
</ListItem> </ListItem>
))} ))}
</List> </List>
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
@ -494,12 +514,17 @@ export const MessageItem = React.memo(
{reactions[selectedReaction]?.find( {reactions[selectedReaction]?.find(
(item) => item?.sender === myAddress (item) => item?.sender === myAddress
) )
? 'Remove Reaction' ? t('core:action.remove_reaction', {
: 'Add Reaction'} postProcess: 'capitalizeFirst',
})
: t('core:action.add_reaction', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
</Box> </Box>
</Popover> </Popover>
)} )}
<Box <Box
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@ -525,8 +550,12 @@ export const MessageItem = React.memo(
}} }}
> >
{message?.status === 'failed-permanent' {message?.status === 'failed-permanent'
? 'Failed to update' ? t('core:message.error.update_failed', {
: 'Updating...'} postProcess: 'capitalizeFirst',
})
: t('core:message.generic.updating', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
) : isTemp ? ( ) : isTemp ? (
<Typography <Typography
@ -537,8 +566,12 @@ export const MessageItem = React.memo(
}} }}
> >
{message?.status === 'failed-permanent' {message?.status === 'failed-permanent'
? 'Failed to send' ? t('core:message.error.send_failed', {
: 'Sending...'} postProcess: 'capitalizeFirst',
})
: t('core:message.generic.sending', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
) : ( ) : (
<> <>
@ -551,7 +584,9 @@ export const MessageItem = React.memo(
fontStyle: 'italic', fontStyle: 'italic',
}} }}
> >
Edited {t('core:message.generic.edited', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}
<Typography <Typography
@ -577,6 +612,7 @@ export const MessageItem = React.memo(
export const ReplyPreview = ({ message, isEdit = false }) => { export const ReplyPreview = ({ message, isEdit = false }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
return ( return (
<Box <Box
@ -612,7 +648,9 @@ export const ReplyPreview = ({ message, isEdit = false }) => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
Editing Message {t('core:message.generic.editing_message', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
) : ( ) : (
<Typography <Typography
@ -621,7 +659,10 @@ export const ReplyPreview = ({ message, isEdit = false }) => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
Replied to {message?.senderName || message?.senderAddress} {t('core:message.generic.replied_to', {
person: message?.senderName || message?.senderAddress,
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}

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 { EditorProvider, useCurrentEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit'; import StarterKit from '@tiptap/starter-kit';
import { Color } from '@tiptap/extension-color'; import { Color } from '@tiptap/extension-color';
@ -31,6 +31,8 @@ import { isDisabledEditorEnterAtom } from '../../atoms/global.js';
import { Box, Checkbox, Typography, useTheme } from '@mui/material'; import { Box, Checkbox, Typography, useTheme } from '@mui/material';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { fileToBase64 } from '../../utils/fileReading/index.js'; import { fileToBase64 } from '../../utils/fileReading/index.js';
import { useTranslation } from 'react-i18next';
import i18next from 'i18next';
function textMatcher(doc, from) { function textMatcher(doc, from) {
const textBeforeCursor = doc.textBetween(0, from, ' ', ' '); const textBeforeCursor = doc.textBetween(0, from, ' ', ' ');
@ -42,7 +44,7 @@ function textMatcher(doc, from) {
return { start, query }; return { start, query };
} }
const MenuBar = React.memo( const MenuBar = memo(
({ ({
setEditorRef, setEditorRef,
isChat, isChat,
@ -52,6 +54,7 @@ const MenuBar = React.memo(
const { editor } = useCurrentEditor(); const { editor } = useCurrentEditor();
const fileInputRef = useRef(null); const fileInputRef = useRef(null);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
useEffect(() => { useEffect(() => {
if (editor && setEditorRef) { if (editor && setEditorRef) {
@ -143,6 +146,7 @@ const MenuBar = React.memo(
> >
<FormatBoldIcon /> <FormatBoldIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
disabled={!editor.can().chain().focus().toggleItalic().run()} disabled={!editor.can().chain().focus().toggleItalic().run()}
@ -155,6 +159,7 @@ const MenuBar = React.memo(
> >
<FormatItalicIcon /> <FormatItalicIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().toggleStrike().run()} onClick={() => editor.chain().focus().toggleStrike().run()}
disabled={!editor.can().chain().focus().toggleStrike().run()} disabled={!editor.can().chain().focus().toggleStrike().run()}
@ -167,6 +172,7 @@ const MenuBar = React.memo(
> >
<StrikethroughSIcon /> <StrikethroughSIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().toggleCode().run()} onClick={() => editor.chain().focus().toggleCode().run()}
disabled={!editor.can().chain().focus().toggleCode().run()} disabled={!editor.can().chain().focus().toggleCode().run()}
@ -179,6 +185,7 @@ const MenuBar = React.memo(
> >
<CodeIcon /> <CodeIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().unsetAllMarks().run()} onClick={() => editor.chain().focus().unsetAllMarks().run()}
sx={{ sx={{
@ -194,6 +201,7 @@ const MenuBar = React.memo(
> >
<FormatClearIcon /> <FormatClearIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
sx={{ sx={{
@ -205,6 +213,7 @@ const MenuBar = React.memo(
> >
<FormatListBulletedIcon /> <FormatListBulletedIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().toggleOrderedList().run()} onClick={() => editor.chain().focus().toggleOrderedList().run()}
sx={{ sx={{
@ -216,6 +225,7 @@ const MenuBar = React.memo(
> >
<FormatListNumberedIcon /> <FormatListNumberedIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().toggleCodeBlock().run()} onClick={() => editor.chain().focus().toggleCodeBlock().run()}
sx={{ sx={{
@ -227,6 +237,7 @@ const MenuBar = React.memo(
> >
<DeveloperModeIcon /> <DeveloperModeIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
sx={{ sx={{
@ -238,6 +249,7 @@ const MenuBar = React.memo(
> >
<FormatQuoteIcon /> <FormatQuoteIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().setHorizontalRule().run()} onClick={() => editor.chain().focus().setHorizontalRule().run()}
disabled={!editor.can().chain().focus().setHorizontalRule().run()} disabled={!editor.can().chain().focus().setHorizontalRule().run()}
@ -245,6 +257,7 @@ const MenuBar = React.memo(
> >
<HorizontalRuleIcon /> <HorizontalRuleIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => onClick={() =>
editor.chain().focus().toggleHeading({ level: 1 }).run() editor.chain().focus().toggleHeading({ level: 1 }).run()
@ -258,6 +271,7 @@ const MenuBar = React.memo(
> >
<FormatHeadingIcon fontSize="small" /> <FormatHeadingIcon fontSize="small" />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().undo().run()} onClick={() => editor.chain().focus().undo().run()}
disabled={!editor.can().chain().focus().undo().run()} disabled={!editor.can().chain().focus().undo().run()}
@ -265,6 +279,7 @@ const MenuBar = React.memo(
> >
<UndoIcon /> <UndoIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => editor.chain().focus().redo().run()} onClick={() => editor.chain().focus().redo().run()}
disabled={!editor.can().chain().focus().redo().run()} disabled={!editor.can().chain().focus().redo().run()}
@ -272,13 +287,14 @@ const MenuBar = React.memo(
> >
<RedoIcon /> <RedoIcon />
</IconButton> </IconButton>
{isChat && ( {isChat && (
<Box <Box
sx={{ sx={{
display: 'flex',
alignItems: 'center', alignItems: 'center',
marginLeft: '5px',
cursor: 'pointer', cursor: 'pointer',
display: 'flex',
marginLeft: '5px',
}} }}
onClick={() => { onClick={() => {
setIsDisabledEditorEnter(!isDisabledEditorEnter); setIsDisabledEditorEnter(!isDisabledEditorEnter);
@ -304,10 +320,13 @@ const MenuBar = React.memo(
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
> >
disable enter {t('core:action.disable_enter', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
{!isChat && ( {!isChat && (
<> <>
<IconButton <IconButton
@ -319,6 +338,7 @@ const MenuBar = React.memo(
> >
<ImageIcon /> <ImageIcon />
</IconButton> </IconButton>
<input <input
type="file" type="file"
ref={fileInputRef} ref={fileInputRef}
@ -348,7 +368,7 @@ const extensions = [
}, },
}), }),
Placeholder.configure({ Placeholder.configure({
placeholder: 'Start typing here...', placeholder: 'Start typing here...', // doesn't work i18next.t('core:action.start_typing'),
}), }),
ImageResize, ImageResize,
]; ];
@ -416,12 +436,6 @@ export default ({
setEditorRef(editorInstance); setEditorRef(editorInstance);
}, []); }, []);
// const users = [
// { id: 1, label: 'Alice' },
// { id: 2, label: 'Bob' },
// { id: 3, label: 'Charlie' },
// ];
const users = useMemo(() => { const users = useMemo(() => {
return (membersWithNames || [])?.map((item) => { return (membersWithNames || [])?.map((item) => {
return { return {

View File

@ -77,26 +77,26 @@ export const CoreSyncStatus = () => {
let imagePath = syncingImg; let imagePath = syncingImg;
let message = t('core:message.status.synchronizing', { let message = t('core:message.status.synchronizing', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}); });
if (isMintingPossible && !isUsingGateway) { if (isMintingPossible && !isUsingGateway) {
imagePath = syncedMintingImg; imagePath = syncedMintingImg;
message = `${t(`core:message.status.${isSynchronizing ? 'synchronizing' : 'synchronized'}`, { postProcess: 'capitalize' })} ${t('core:message.status.minting')}`; message = `${t(`core:message.status.${isSynchronizing ? 'synchronizing' : 'synchronized'}`, { postProcess: 'capitalizeFirst' })} ${t('core:message.status.minting')}`;
} else if (isSynchronizing === true && syncPercent === 99) { } else if (isSynchronizing === true && syncPercent === 99) {
imagePath = syncingImg; imagePath = syncingImg;
} else if (isSynchronizing && !isMintingPossible && syncPercent === 100) { } else if (isSynchronizing && !isMintingPossible && syncPercent === 100) {
imagePath = syncingImg; imagePath = syncingImg;
message = `${t('core:message.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.not_minting') : ''}`; message = `${t('core:message.status.synchronizing', { postProcess: 'capitalizeFirst' })} ${!isUsingGateway ? t('core:message.status.not_minting') : ''}`;
} else if (!isSynchronizing && !isMintingPossible && syncPercent === 100) { } else if (!isSynchronizing && !isMintingPossible && syncPercent === 100) {
imagePath = syncedImg; imagePath = syncedImg;
message = `${t('core:message.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.not_minting') : ''}`; message = `${t('core:message.status.synchronized', { postProcess: 'capitalizeFirst' })} ${!isUsingGateway ? t('core:message.status.not_minting') : ''}`;
} else if (isSynchronizing && isMintingPossible && syncPercent === 100) { } else if (isSynchronizing && isMintingPossible && syncPercent === 100) {
imagePath = syncingImg; imagePath = syncingImg;
message = `${t('core:message.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.minting') : ''}`; message = `${t('core:message.status.synchronizing', { postProcess: 'capitalizeFirst' })} ${!isUsingGateway ? t('core:message.status.minting') : ''}`;
} else if (!isSynchronizing && isMintingPossible && syncPercent === 100) { } else if (!isSynchronizing && isMintingPossible && syncPercent === 100) {
imagePath = syncedMintingImg; imagePath = syncedMintingImg;
message = `${t('core:message.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.minting') : ''}`; message = `${t('core:message.status.synchronized', { postProcess: 'capitalizeFirst' })} ${!isUsingGateway ? t('core:message.status.minting') : ''}`;
} }
return ( return (
@ -121,36 +121,38 @@ export const CoreSyncStatus = () => {
top: '10px', top: '10px',
}} }}
> >
<h3>{t('core:core.information', { postProcess: 'capitalize' })}</h3> <h3>
{t('core:core.information', { postProcess: 'capitalizeFirst' })}
</h3>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('core:core.version', { postProcess: 'capitalize' })}:{' '} {t('core:core.version', { postProcess: 'capitalizeFirst' })}:{' '}
<span style={{ color: '#03a9f4' }}>{buildVersion}</span> <span style={{ color: '#03a9f4' }}>{buildVersion}</span>
</h4> </h4>
<h4 className="lineHeight">{message}</h4> <h4 className="lineHeight">{message}</h4>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('core:core.block_height', { postProcess: 'capitalize' })}:{' '} {t('core:core.block_height', { postProcess: 'capitalizeFirst' })}:{' '}
<span style={{ color: '#03a9f4' }}>{height || ''}</span> <span style={{ color: '#03a9f4' }}>{height || ''}</span>
</h4> </h4>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('core:core.peers', { postProcess: 'capitalize' })}:{' '} {t('core:core.peers', { postProcess: 'capitalizeFirst' })}:{' '}
<span style={{ color: '#03a9f4' }}> <span style={{ color: '#03a9f4' }}>
{numberOfConnections || ''} {numberOfConnections || ''}
</span> </span>
</h4> </h4>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('auth:node.using_public', { postProcess: 'capitalize' })}:{' '} {t('auth:node.using_public', { postProcess: 'capitalizeFirst' })}:{' '}
<span style={{ color: '#03a9f4' }}> <span style={{ color: '#03a9f4' }}>
{isUsingGateway?.toString()} {isUsingGateway?.toString()}
</span> </span>
</h4> </h4>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('core:ui.version', { postProcess: 'capitalize' })}:{' '} {t('core:ui.version', { postProcess: 'capitalizeFirst' })}:{' '}
<span style={{ color: '#03a9f4' }}>{manifestData.version}</span> <span style={{ color: '#03a9f4' }}>{manifestData.version}</span>
</h4> </h4>
</div> </div>

View File

@ -46,7 +46,7 @@ export const Explore = ({ setDesktopViewMode }) => {
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
{t('tutorial:initial.trade_qort', { postProcess: 'capitalize' })} {t('tutorial:initial.trade_qort', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
@ -73,7 +73,7 @@ export const Explore = ({ setDesktopViewMode }) => {
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
{t('tutorial:initial.see_apps', { postProcess: 'capitalize' })} {t('tutorial:initial.see_apps', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
@ -102,7 +102,9 @@ export const Explore = ({ setDesktopViewMode }) => {
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
{t('tutorial:initial.general_chat', { postProcess: 'capitalize' })} {t('tutorial:initial.general_chat', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
@ -129,7 +131,7 @@ export const Explore = ({ setDesktopViewMode }) => {
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
{t('core:wallet.wallet_other', { postProcess: 'capitalize' })} {t('core:wallet.wallet_other', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
</Box> </Box>

View File

@ -111,7 +111,7 @@ export const GeneralNotifications = ({ address }) => {
userSelect: 'none', userSelect: 'none',
}} }}
> >
No new notifications {t('core:message.generic.no_notifications')}
</Typography> </Typography>
)} )}
{hasNewPayment && ( {hasNewPayment && (

View File

@ -70,9 +70,9 @@ export const JoinGroup = () => {
const fee = await getFee('JOIN_GROUP'); const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'JOIN_GROUP', action: 'JOIN_GROUP',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -89,7 +89,7 @@ export const JoinGroup = () => {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.group_join', { message: t('group:message.success.group_join', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
@ -100,11 +100,11 @@ export const JoinGroup = () => {
type: 'joined-group', type: 'joined-group',
label: t('group:message.success.group_join_label', { label: t('group:message.success.group_join_label', {
group_name: group?.groupName, group_name: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
labelDone: t('group:message.success.group_join_label', { labelDone: t('group:message.success.group_join_label', {
group_name: group?.groupName, group_name: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
done: false, done: false,
groupId, groupId,
@ -118,11 +118,11 @@ export const JoinGroup = () => {
type: 'joined-group-request', type: 'joined-group-request',
label: t('group:message.success.group_join_request', { label: t('group:message.success.group_join_request', {
group_name: group?.groupName, group_name: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
labelDone: t('group:message.success.group_join_outcome', { labelDone: t('group:message.success.group_join_outcome', {
group_name: group?.groupName, group_name: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
done: false, done: false,
groupId, groupId,
@ -147,7 +147,9 @@ export const JoinGroup = () => {
type: 'error', type: 'error',
message: message:
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }), t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -205,7 +207,7 @@ export const JoinGroup = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
{t('group:group.name', { postProcess: 'capitalize' })}:{' '} {t('group:group.name', { postProcess: 'capitalizeFirst' })}:{' '}
{` ${groupInfo?.groupName}`} {` ${groupInfo?.groupName}`}
</Typography> </Typography>
@ -215,8 +217,10 @@ export const JoinGroup = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
{t('group:group.member_number', { postProcess: 'capitalize' })}:{' '} {t('group:group.member_number', {
{` ${groupInfo?.memberCount}`} postProcess: 'capitalizeFirst',
})}
: {` ${groupInfo?.memberCount}`}
</Typography> </Typography>
{groupInfo?.description && ( {groupInfo?.description && (
@ -237,7 +241,7 @@ export const JoinGroup = () => {
}} }}
> >
{t('group:message.generic.already_in_group', { {t('group:message.generic.already_in_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
)} )}
@ -249,7 +253,7 @@ export const JoinGroup = () => {
}} }}
> >
{t('group:message.generic.closed_group', { {t('group:message.generic.closed_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
)} )}
@ -276,7 +280,7 @@ export const JoinGroup = () => {
}} }}
> >
{t('core:action.join', { {t('core:action.join', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</CustomButtonAccept> </CustomButtonAccept>
</ButtonBase> </ButtonBase>
@ -291,7 +295,7 @@ export const JoinGroup = () => {
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
> >
{t('core:action.close', { {t('core:action.close', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</CustomButtonAccept> </CustomButtonAccept>
</DialogActions> </DialogActions>

View File

@ -105,22 +105,22 @@ export const AddGroup = ({ address, open, setOpen }) => {
if (!name) if (!name)
throw new Error( throw new Error(
t('group:message.error.name_required', { t('group:message.error.name_required', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
); );
if (!description) if (!description)
throw new Error( throw new Error(
t('group:message.error.description_required', { t('group:message.error.description_required', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
); );
const fee = await getFee('CREATE_GROUP'); const fee = await getFee('CREATE_GROUP');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'CREATE_GROUP', action: 'CREATE_GROUP',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -140,7 +140,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.group_creation', { message: t('group:message.success.group_creation', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -150,11 +150,11 @@ export const AddGroup = ({ address, open, setOpen }) => {
type: 'created-group', type: 'created-group',
label: t('group:message.success.group_creation_name', { label: t('group:message.success.group_creation_name', {
group_name: name, group_name: name,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
labelDone: t('group:message.success.group_creation_label', { labelDone: t('group:message.success.group_creation_label', {
group_name: name, group_name: name,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
done: false, done: false,
}, },
@ -172,7 +172,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
rej({ rej({
message: message:
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }), t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
}); });
}); });
@ -225,7 +227,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Toolbar> <Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div"> <Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
{t('group:group.management', { postProcess: 'capitalize' })} {t('group:group.management', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
<IconButton <IconButton
@ -267,7 +269,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Tab <Tab
label={t('group:action.create_group', { label={t('group:action.create_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
{...a11yProps(0)} {...a11yProps(0)}
sx={{ sx={{
@ -279,7 +281,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
/> />
<Tab <Tab
label={t('group:action.find_group', { label={t('group:action.find_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
{...a11yProps(1)} {...a11yProps(1)}
sx={{ sx={{
@ -291,7 +293,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
/> />
<Tab <Tab
label={t('group:group.invites', { label={t('group:group.invites', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
{...a11yProps(2)} {...a11yProps(2)}
sx={{ sx={{
@ -328,13 +330,13 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Label> <Label>
{t('group:group.name', { {t('group:group.name', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Label> </Label>
<Input <Input
placeholder={t('group:group.name', { placeholder={t('group:group.name', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
@ -350,13 +352,13 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Label> <Label>
{t('group:group.description', { {t('group:group.description', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Label> </Label>
<Input <Input
placeholder={t('group:group.description', { placeholder={t('group:group.description', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
@ -371,9 +373,8 @@ export const AddGroup = ({ address, open, setOpen }) => {
}} }}
> >
<Label> <Label>
{' '}
{t('group:group.type', { {t('group:group.type', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Label> </Label>
@ -386,12 +387,12 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<MenuItem value={1}> <MenuItem value={1}>
{t('group:group.open', { {t('group:group.open', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</MenuItem> </MenuItem>
<MenuItem value={0}> <MenuItem value={0}>
{t('group:group.closed', { {t('group:group.closed', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</MenuItem> </MenuItem>
</Select> </Select>
@ -408,7 +409,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Typography> <Typography>
{t('group:advanced_options', { {t('group:advanced_options', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
@ -425,9 +426,10 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Label> <Label>
{t('group:approval_threshold', { {t('group:approval_threshold', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Label> </Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
@ -437,12 +439,12 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<MenuItem value={0}> <MenuItem value={0}>
{t('core:count.none', { {t('core:count.none', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</MenuItem> </MenuItem>
<MenuItem value={1}> <MenuItem value={1}>
{t('core:count.one', { {t('core:count.one', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</MenuItem> </MenuItem>
<MenuItem value={20}>20%</MenuItem> <MenuItem value={20}>20%</MenuItem>
@ -462,7 +464,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Label> <Label>
{t('group:block_delay.minimum', { {t('group:block_delay.minimum', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Label> </Label>
@ -521,7 +523,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Label> <Label>
{t('group:block_delay.maximum', { {t('group:block_delay.maximum', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Label> </Label>
@ -536,7 +538,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
{t('core:time.hour', { count: 1 })} {t('core:time.hour', { count: 1 })}
</MenuItem> </MenuItem>
<MenuItem value={180}> <MenuItem value={180}>
3{t('core:time.hour', { count: 3 })} {t('core:time.hour', { count: 3 })}
</MenuItem> </MenuItem>
<MenuItem value={300}> <MenuItem value={300}>
{t('core:time.hour', { count: 5 })} {t('core:time.hour', { count: 5 })}
@ -582,7 +584,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
onClick={handleCreateGroup} onClick={handleCreateGroup}
> >
{t('group:action.create_group', { {t('group:action.create_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Button> </Button>
</Box> </Box>

View File

@ -113,9 +113,9 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const fee = await getFee('JOIN_GROUP'); const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'JOIN_GROUP', action: 'JOIN_GROUP',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -131,7 +131,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.join_group', { message: t('group:message.success.join_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
@ -142,11 +142,11 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
type: 'joined-group', type: 'joined-group',
label: t('group:message.success.group_join_label', { label: t('group:message.success.group_join_label', {
group_name: group?.groupName, group_name: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
labelDone: t('group:message.success.group_join_label', { labelDone: t('group:message.success.group_join_label', {
group_name: group?.groupName, group_name: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
done: false, done: false,
groupId, groupId,
@ -160,11 +160,11 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
type: 'joined-group-request', type: 'joined-group-request',
label: t('group:message.success.group_join_request', { label: t('group:message.success.group_join_request', {
group_name: group?.groupName, group_name: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
labelDone: t('group:message.success.group_join_outcome', { labelDone: t('group:message.success.group_join_outcome', {
group_name: group?.groupName, group_name: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
done: false, done: false,
groupId, groupId,
@ -243,7 +243,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
}} }}
> >
<Typography> <Typography>
{t('core:action.join', { postProcess: 'capitalize' })}{' '} {t('core:action.join', { postProcess: 'capitalizeFirst' })}{' '}
{group?.groupName} {group?.groupName}
</Typography> </Typography>
<Typography> <Typography>
@ -257,7 +257,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
onClick={() => handleJoinGroup(group, group?.isOpen)} onClick={() => handleJoinGroup(group, group?.isOpen)}
> >
{t('group:action.join_group', { {t('group:action.join_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</Box> </Box>

View File

@ -170,7 +170,9 @@ export const GroupMail = ({
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -754,7 +756,7 @@ export const GroupMail = ({
<ThreadSingleLastMessageP> <ThreadSingleLastMessageP>
<ThreadSingleLastMessageSpanP> <ThreadSingleLastMessageSpanP>
{t('group:last_message', { {t('group:last_message', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
:{' '} :{' '}
</ThreadSingleLastMessageSpanP> </ThreadSingleLastMessageSpanP>
@ -791,7 +793,7 @@ export const GroupMail = ({
}} }}
> >
{t('core:page.last', { {t('core:page.last', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
<ArrowForwardIosIcon <ArrowForwardIosIcon
@ -825,7 +827,7 @@ export const GroupMail = ({
open={isLoading} open={isLoading}
info={{ info={{
message: t('group:message.success.loading_threads', { message: t('group:message.success.loading_threads', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}} }}
/> />

View File

@ -182,29 +182,29 @@ export const NewThread = ({
const missingFields: string[] = []; const missingFields: string[] = [];
if (!isMessage && !threadTitle) { if (!isMessage && !threadTitle) {
errorMsg = t('group:question.provide_thread', { errorMsg = t('core:message.question.provide_thread', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}); });
} }
if (!name) { if (!name) {
errorMsg = t('group:message.error.access_name', { errorMsg = t('group:message.error.access_name', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}); });
} }
if (!groupInfo) { if (!groupInfo) {
errorMsg = t('group:message.error.group_info', { errorMsg = t('group:message.error.group_info', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}); });
} }
// if (!description) missingFields.push('subject') // if (!description) missingFields.push('subject')
if (missingFields.length > 0) { if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', '); const missingFieldsString = missingFields.join(', ');
const errMsg = t('group:message.error.missing_field', { const errMsg = t('core:message.error.missing_fields', {
field: missingFieldsString, field: missingFieldsString,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}); });
errorMsg = errMsg; errorMsg = errMsg;
} }
@ -217,7 +217,7 @@ export const NewThread = ({
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') { if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') {
const errMsg = t('group:message.generic.provide_message', { const errMsg = t('group:message.generic.provide_message', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}); });
throw new Error(errMsg); throw new Error(errMsg);
} }
@ -229,9 +229,9 @@ export const NewThread = ({
feeToShow = +feeToShow * 2; feeToShow = +feeToShow * 2;
} }
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'ARBITRARY', action: 'ARBITRARY',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: feeToShow + ' QORT', publishFee: feeToShow + ' QORT',
}); });
@ -257,7 +257,7 @@ export const NewThread = ({
isPrivate === false ? null : await getSecretKey(false, true); isPrivate === false ? null : await getSecretKey(false, true);
if (!secretKey && isPrivate) { if (!secretKey && isPrivate) {
const errMsg = t('group:message.error.group_secret_key', { const errMsg = t('group:message.error.group_secret_key', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}); });
throw new Error(errMsg); throw new Error(errMsg);
} }
@ -320,7 +320,7 @@ export const NewThread = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.thread_creation', { message: t('group:message.success.thread_creation', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -332,7 +332,7 @@ export const NewThread = ({
} else { } else {
if (!currentThread) { if (!currentThread) {
const errMsg = t('group:message.error.thread_id', { const errMsg = t('group:message.error.thread_id', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}); });
throw new Error(errMsg); throw new Error(errMsg);
} }
@ -360,7 +360,7 @@ export const NewThread = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.post_creation', { message: t('group:message.success.post_creation', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -404,10 +404,10 @@ export const NewThread = ({
<ComposeP> <ComposeP>
{currentThread {currentThread
? t('core:action.new.post', { ? t('core:action.new.post', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
: t('core:action.new.thread', { : t('core:action.new.thread', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</ComposeP> </ComposeP>
</ComposeContainer> </ComposeContainer>
@ -437,10 +437,10 @@ export const NewThread = ({
<NewMessageHeaderP> <NewMessageHeaderP>
{isMessage {isMessage
? t('core:action.post_message', { ? t('core:action.post_message', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
: t('core:action.new.thread', { : t('core:action.new.thread', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</NewMessageHeaderP> </NewMessageHeaderP>
@ -562,10 +562,10 @@ export const NewThread = ({
<NewMessageSendP> <NewMessageSendP>
{isMessage {isMessage
? t('core:action.post', { ? t('core:action.post', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
: t('core:action.create_thread', { : t('core:action.create_thread', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</NewMessageSendP> </NewMessageSendP>

View File

@ -609,7 +609,7 @@ export const Thread = ({
<ReturnIcon /> <ReturnIcon />
<ComposeP> <ComposeP>
{t('group:action.return_to_thread', { {t('group:action.return_to_thread', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</ComposeP> </ComposeP>
</ShowMessageReturnButton> </ShowMessageReturnButton>
@ -688,7 +688,7 @@ export const Thread = ({
disabled={!hasFirstPage} disabled={!hasFirstPage}
variant="contained" variant="contained"
> >
{t('core:page.first', { postProcess: 'capitalize' })} {t('core:page.first', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -707,7 +707,7 @@ export const Thread = ({
disabled={!hasPreviousPage} disabled={!hasPreviousPage}
variant="contained" variant="contained"
> >
{t('core:page.previous', { postProcess: 'capitalize' })} {t('core:page.previous', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -726,7 +726,7 @@ export const Thread = ({
disabled={!hasNextPage} disabled={!hasNextPage}
variant="contained" variant="contained"
> >
{t('core:page.next', { postProcess: 'capitalize' })} {t('core:page.next', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -745,7 +745,7 @@ export const Thread = ({
disabled={!hasLastPage} disabled={!hasLastPage}
variant="contained" variant="contained"
> >
{t('core:page.last', { postProcess: 'capitalize' })} {t('core:page.last', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</Box> </Box>
@ -929,7 +929,9 @@ export const Thread = ({
fontSize: '18px', fontSize: '18px',
}} }}
> >
{t('core:downloading_qdn', { postProcess: 'capitalize' })} {t('core:downloading_qdn', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@ -961,7 +963,7 @@ export const Thread = ({
}} }}
> >
{t('group:action.refetch_page', { {t('group:action.refetch_page', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Button> </Button>
</Box> </Box>
@ -1000,7 +1002,7 @@ export const Thread = ({
disabled={!hasFirstPage} disabled={!hasFirstPage}
variant="contained" variant="contained"
> >
{t('core:page.first', { postProcess: 'capitalize' })} {t('core:page.first', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -1019,7 +1021,7 @@ export const Thread = ({
disabled={!hasPreviousPage} disabled={!hasPreviousPage}
variant="contained" variant="contained"
> >
{t('core:page.previous', { postProcess: 'capitalize' })} {t('core:page.previous', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -1038,7 +1040,7 @@ export const Thread = ({
disabled={!hasNextPage} disabled={!hasNextPage}
variant="contained" variant="contained"
> >
{t('core:page.next', { postProcess: 'capitalize' })} {t('core:page.next', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -1057,7 +1059,7 @@ export const Thread = ({
disabled={!hasLastPage} disabled={!hasLastPage}
variant="contained" variant="contained"
> >
{t('core:page.last', { postProcess: 'capitalize' })} {t('core:page.last', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</Box> </Box>
@ -1071,7 +1073,7 @@ export const Thread = ({
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{
message: t('core:loading_posts', { postProcess: 'capitalize' }), message: t('core:loading.posts', { postProcess: 'capitalizeFirst' }),
}} }}
/> />
</GroupContainer> </GroupContainer>

View File

@ -373,7 +373,6 @@ export const Group = ({
desktopViewMode, desktopViewMode,
}: GroupProps) => { }: GroupProps) => {
const [desktopSideView, setDesktopSideView] = useState('groups'); const [desktopSideView, setDesktopSideView] = useState('groups');
const [secretKey, setSecretKey] = useState(null); const [secretKey, setSecretKey] = useState(null);
const [secretKeyPublishDate, setSecretKeyPublishDate] = useState(null); const [secretKeyPublishDate, setSecretKeyPublishDate] = useState(null);
const lastFetchedSecretKey = useRef(null); const lastFetchedSecretKey = useRef(null);
@ -396,14 +395,11 @@ export const Group = ({
const [openAddGroup, setOpenAddGroup] = useState(false); const [openAddGroup, setOpenAddGroup] = useState(false);
const [isInitialGroups, setIsInitialGroups] = useState(false); const [isInitialGroups, setIsInitialGroups] = useState(false);
const [openManageMembers, setOpenManageMembers] = useState(false); const [openManageMembers, setOpenManageMembers] = useState(false);
const setMemberGroups = useSetAtom(memberGroupsAtom); const setMemberGroups = useSetAtom(memberGroupsAtom);
const lastGroupNotification = useRef<null | number>(null); const lastGroupNotification = useRef<null | number>(null);
const [timestampEnterData, setTimestampEnterData] = useAtom( const [timestampEnterData, setTimestampEnterData] = useAtom(
timestampEnterDataAtom timestampEnterDataAtom
); );
const [chatMode, setChatMode] = useState('groups'); const [chatMode, setChatMode] = useState('groups');
const [newChat, setNewChat] = useState(false); const [newChat, setNewChat] = useState(false);
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
@ -514,7 +510,9 @@ export const Group = ({
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -543,7 +541,9 @@ export const Group = ({
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -575,7 +575,9 @@ export const Group = ({
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -1098,7 +1100,9 @@ export const Group = ({
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -1995,7 +1999,7 @@ export const Group = ({
}} }}
> >
{t('group:message.generic.no_selection', { {t('group:message.generic.no_selection', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</Box> </Box>
@ -2094,7 +2098,7 @@ export const Group = ({
{' '} {' '}
<Typography> <Typography>
{t('group:message.generic.encryption_key', { {t('group:message.generic.encryption_key', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</div> </div>
@ -2120,21 +2124,21 @@ export const Group = ({
{' '} {' '}
<Typography> <Typography>
{t('group:message.generic.not_part_group', { {t('group:message.generic.not_part_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
<Spacer height="25px" /> <Spacer height="25px" />
<Typography> <Typography>
<strong> <strong>
{t('group:message.generic.only_encrypted', { {t('group:message.generic.only_encrypted', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</strong> </strong>
</Typography> </Typography>
<Spacer height="25px" /> <Spacer height="25px" />
<Typography> <Typography>
{t('group:message.generic.notify_admins', { {t('group:message.generic.notify_admins', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
<Spacer height="25px" /> <Spacer height="25px" />
@ -2156,7 +2160,7 @@ export const Group = ({
onClick={() => notifyAdmin(admin)} onClick={() => notifyAdmin(admin)}
> >
{t('core:action.notify', { {t('core:action.notify', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</Box> </Box>
@ -2382,7 +2386,7 @@ export const Group = ({
message: message:
isLoadingGroupMessage || isLoadingGroupMessage ||
t('group:message.generic.setting_group', { t('group:message.generic.setting_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}} }}
/> />
@ -2391,7 +2395,7 @@ export const Group = ({
open={isLoadingGroups} open={isLoadingGroups}
info={{ info={{
message: t('group:message.generic.setting_group', { message: t('group:message.generic.setting_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}} }}
/> />

View File

@ -71,7 +71,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
{t('group:group.invites', { postProcess: 'capitalize' })}{' '} {t('group:group.invites', { postProcess: 'capitalizeFirst' })}{' '}
{groupsWithJoinRequests?.length > 0 && {groupsWithJoinRequests?.length > 0 &&
` (${groupsWithJoinRequests?.length})`} ` (${groupsWithJoinRequests?.length})`}
</Typography> </Typography>
@ -131,7 +131,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
}} }}
> >
{t('group:message.generic.no_display', { {t('group:message.generic.no_display', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</Box> </Box>
@ -181,7 +181,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
}} }}
primary={t('group:message.generic.group_invited_you', { primary={t('group:message.generic.group_invited_you', {
group: group?.groupName, group: group?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
/> />
</ListItemButton> </ListItemButton>

View File

@ -144,7 +144,7 @@ export const GroupJoinRequests = ({
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
{t('group:join_requests', { postProcess: 'capitalize' })}{' '} {t('group:join_requests', { postProcess: 'capitalizeFirst' })}{' '}
{filteredJoinRequests?.filter((group) => group?.data?.length > 0) {filteredJoinRequests?.filter((group) => group?.data?.length > 0)
?.length > 0 && ?.length > 0 &&
` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`} ` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`}
@ -207,7 +207,7 @@ export const GroupJoinRequests = ({
}} }}
> >
{t('group:message.generic.no_display', { {t('group:message.generic.no_display', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</Box> </Box>

View File

@ -32,6 +32,7 @@ import {
import { timeDifferenceForNotificationChats } from './Group'; import { timeDifferenceForNotificationChats } from './Group';
import { useAtom, useAtomValue } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import { useTranslation } from 'react-i18next';
export const GroupList = ({ export const GroupList = ({
selectGroupFunc, selectGroupFunc,
@ -49,6 +50,7 @@ export const GroupList = ({
myAddress, myAddress,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom); const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom);
return ( return (
@ -86,7 +88,9 @@ export const GroupList = ({
? theme.palette.text.primary ? theme.palette.text.primary
: theme.palette.text.secondary : theme.palette.text.secondary
} }
label="Groups" label={t('group:group.group_other', {
postProcess: 'capitalizeFirst',
})}
selected={desktopSideView === 'groups'} selected={desktopSideView === 'groups'}
customWidth="75px" customWidth="75px"
> >
@ -102,6 +106,7 @@ export const GroupList = ({
/> />
</IconWrapper> </IconWrapper>
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setDesktopSideView('directs'); setDesktopSideView('directs');
@ -116,7 +121,9 @@ export const GroupList = ({
? theme.palette.text.primary ? theme.palette.text.primary
: theme.palette.text.secondary : theme.palette.text.secondary
} }
label="Messaging" label={t('group:group.messaging', {
postProcess: 'capitalizeFirst',
})}
selected={desktopSideView === 'directs'} selected={desktopSideView === 'directs'}
> >
<MessagingIcon <MessagingIcon
@ -165,6 +172,7 @@ export const GroupList = ({
))} ))}
</List> </List>
</div> </div>
<div <div
style={{ style={{
display: 'flex', display: 'flex',
@ -185,7 +193,7 @@ export const GroupList = ({
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
/> />
Group {t('group:group.group', { postProcess: 'capitalizeFirst' })}
</CustomButton> </CustomButton>
{!isRunningPublicNode && ( {!isRunningPublicNode && (
@ -273,6 +281,7 @@ const GroupItem = React.memo(
</Avatar> </Avatar>
)} )}
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={group.groupId === '0' ? 'General' : group.groupName} primary={group.groupId === '0' ? 'General' : group.groupName}
secondary={ secondary={
@ -302,6 +311,7 @@ const GroupItem = React.memo(
fontSize: '16px', fontSize: '16px',
}} }}
/> />
{announcement && !announcement?.seentimestamp && ( {announcement && !announcement?.seentimestamp && (
<CampaignIcon <CampaignIcon
sx={{ sx={{
@ -311,6 +321,7 @@ const GroupItem = React.memo(
}} }}
/> />
)} )}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -335,6 +346,7 @@ const GroupItem = React.memo(
}} }}
/> />
)} )}
{groupProperty?.isOpen === false && ( {groupProperty?.isOpen === false && (
<LockIcon <LockIcon
sx={{ sx={{

View File

@ -85,7 +85,7 @@ export const HomeDesktop = ({
padding: '10px', padding: '10px',
}} }}
> >
{t('core:welcome', { postProcess: 'capitalize' })} {t('core:welcome', { postProcess: 'capitalizeFirst' })}
{userInfo?.name ? ( {userInfo?.name ? (
<span <span
style={{ style={{
@ -217,7 +217,9 @@ export const HomeDesktop = ({
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
{t('tutorial:initial.explore', { postProcess: 'capitalize' })} {t('tutorial:initial.explore', {
postProcess: 'capitalizeFirst',
})}
</Typography>{' '} </Typography>{' '}
</Box> </Box>
</Divider> </Divider>

View File

@ -15,14 +15,17 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
const inviteMember = async () => { const inviteMember = async () => {
try { try {
const fee = await getFee('GROUP_INVITE'); const fee = await getFee('GROUP_INVITE');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'GROUP_INVITE', action: 'GROUP_INVITE',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingInvite(true); setIsLoadingInvite(true);
if (!expiryTime || !value) return; if (!expiryTime || !value) return;
new Promise((res, rej) => { new Promise((res, rej) => {
window window
@ -37,12 +40,11 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
type: 'success', type: 'success',
message: t('group:message.success.group_invite', { message: t('group:message.success.group_invite', {
value: value, value: value,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
res(response); res(response);
setValue(''); setValue('');
return; return;
} }
@ -56,7 +58,11 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error?.message || 'An error occurred', message:
error?.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -80,22 +86,27 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
flexDirection: 'column', flexDirection: 'column',
}} }}
> >
{t('group:action.invite_member', { postProcess: 'capitalize' })} {t('group:action.invite_member', { postProcess: 'capitalizeFirst' })}
<Spacer height="20px" /> <Spacer height="20px" />
<Input <Input
value={value} value={value}
placeholder="Name or address" placeholder="Name or address"
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
/> />
<Spacer height="20px" /> <Spacer height="20px" />
<Label> <Label>
{t('group:invitation_expiry', { postProcess: 'capitalize' })} {t('group:invitation_expiry', { postProcess: 'capitalizeFirst' })}
</Label> </Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
value={expiryTime} value={expiryTime}
label={t('group:invitation_expiry', { postProcess: 'capitalize' })} label={t('group:invitation_expiry', { postProcess: 'capitalizeFirst' })}
onChange={handleChange} onChange={handleChange}
> >
<MenuItem value={10800}>{t('core:time.hour', { count: 3 })}</MenuItem> <MenuItem value={10800}>{t('core:time.hour', { count: 3 })}</MenuItem>
@ -109,14 +120,16 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
<MenuItem value={1296000}>{t('core:time.day', { count: 15 })}</MenuItem> <MenuItem value={1296000}>{t('core:time.day', { count: 15 })}</MenuItem>
<MenuItem value={2592000}>{t('core:time.day', { count: 30 })}</MenuItem> <MenuItem value={2592000}>{t('core:time.day', { count: 30 })}</MenuItem>
</Select> </Select>
<Spacer height="20px" /> <Spacer height="20px" />
<LoadingButton <LoadingButton
variant="contained" variant="contained"
loadingPosition="start" loadingPosition="start"
loading={isLoadingInvite} loading={isLoadingInvite}
onClick={inviteMember} onClick={inviteMember}
> >
{t('core:action.invite', { postProcess: 'capitalize' })} {t('core:action.invite', { postProcess: 'capitalizeFirst' })}
</LoadingButton> </LoadingButton>
</Box> </Box>
); );

View File

@ -88,9 +88,9 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
try { try {
const fee = await getFee('CANCEL_GROUP_BAN'); const fee = await getFee('CANCEL_GROUP_BAN');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'CANCEL_GROUP_BAN', action: 'CANCEL_GROUP_BAN',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -108,7 +108,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.unbanned_user', { message: t('group:message.success.unbanned_user', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
handlePopoverClose(); handlePopoverClose();
@ -184,7 +184,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
onClick={() => handleCancelBan(member?.offender)} onClick={() => handleCancelBan(member?.offender)}
> >
{t('group:action.cancel_ban', { {t('group:action.cancel_ban', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</Box> </Box>
@ -214,7 +214,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
return ( return (
<div> <div>
<p>{t('group:ban_list', { postProcess: 'capitalize' })}</p> <p>{t('group:ban_list', { postProcess: 'capitalizeFirst' })}</p>
<div <div
style={{ style={{
display: 'flex', display: 'flex',

View File

@ -34,7 +34,6 @@ import {
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { CustomLoader } from '../../common/CustomLoader'; import { CustomLoader } from '../../common/CustomLoader';
import { RequestQueueWithPromise } from '../../utils/queue/queue'; import { RequestQueueWithPromise } from '../../utils/queue/queue';
import { import {
myGroupsWhereIAmAdminAtom, myGroupsWhereIAmAdminAtom,
promotionTimeIntervalAtom, promotionTimeIntervalAtom,
@ -89,9 +88,7 @@ export const ListOfGroupPromotions = () => {
const [promotionTimeInterval, setPromotionTimeInterval] = useAtom( const [promotionTimeInterval, setPromotionTimeInterval] = useAtom(
promotionTimeIntervalAtom promotionTimeIntervalAtom
); );
const [isExpanded, setIsExpanded] = React.useState(false); const [isExpanded, setIsExpanded] = React.useState(false);
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [fee, setFee] = useState(null); const [fee, setFee] = useState(null);
@ -99,7 +96,6 @@ export const ListOfGroupPromotions = () => {
const [isLoadingPublish, setIsLoadingPublish] = useState(false); const [isLoadingPublish, setIsLoadingPublish] = useState(false);
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
const setTxList = useSetAtom(txListAtom); const setTxList = useSetAtom(txListAtom);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']); const { t } = useTranslation(['core', 'group']);
const listRef = useRef(null); const listRef = useRef(null);
@ -245,14 +241,17 @@ export const ListOfGroupPromotions = () => {
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_promotion', {
'Successfully published promotion. It may take a couple of minutes for the promotion to appear', postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
setText(''); setText('');
@ -262,7 +261,10 @@ export const ListOfGroupPromotions = () => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: message:
error?.message || 'Error publishing the promotion. Please try again', error?.message ||
t('group:message.error.group_promotion', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
} finally { } finally {
@ -275,9 +277,9 @@ export const ListOfGroupPromotions = () => {
const groupId = group.groupId; const groupId = group.groupId;
const fee = await getFee('JOIN_GROUP'); const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'JOIN_GROUP', action: 'JOIN_GROUP',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -291,8 +293,9 @@ export const ListOfGroupPromotions = () => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_join', {
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalizeFirst',
}),
}); });
if (isOpen) { if (isOpen) {
@ -300,8 +303,14 @@ export const ListOfGroupPromotions = () => {
{ {
...response, ...response,
type: 'joined-group', type: 'joined-group',
label: `Joined Group ${group?.groupName}: awaiting confirmation`, label: t('group:message.success.group_join_label', {
labelDone: `Joined Group ${group?.groupName}: success!`, group_name: group?.groupName,
postProcess: 'capitalizeFirst',
}),
labelDone: t('group:message.success.group_join_label', {
group_name: group?.groupName,
postProcess: 'capitalizeFirst',
}),
done: false, done: false,
groupId, groupId,
}, },
@ -312,15 +321,20 @@ export const ListOfGroupPromotions = () => {
{ {
...response, ...response,
type: 'joined-group-request', type: 'joined-group-request',
label: `Requested to join Group ${group?.groupName}: awaiting confirmation`, label: t('group:message.success.group_join_request', {
labelDone: `Requested to join Group ${group?.groupName}: success!`, group_name: group?.groupName,
postProcess: 'capitalizeFirst',
}),
labelDone: t('group:message.success.group_join_outcome', {
group_name: group?.groupName,
postProcess: 'capitalizeFirst',
}),
done: false, done: false,
groupId, groupId,
}, },
...prev, ...prev,
]); ]);
} }
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
res(response); res(response);
@ -337,7 +351,11 @@ export const ListOfGroupPromotions = () => {
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -386,7 +404,7 @@ export const ListOfGroupPromotions = () => {
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
Group promotions{' '} {t('group:group.promotions', { postProcess: 'capitalizeFirst' })}{' '}
{promotions.length > 0 && ` (${promotions.length})`} {promotions.length > 0 && ` (${promotions.length})`}
</Typography> </Typography>
@ -445,7 +463,9 @@ export const ListOfGroupPromotions = () => {
fontSize: '12px', fontSize: '12px',
}} }}
> >
Add Promotion {t('group.action.add_promotion', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
</Box> </Box>
@ -491,7 +511,9 @@ export const ListOfGroupPromotions = () => {
color: 'rgba(255, 255, 255, 0.2)', color: 'rgba(255, 255, 255, 0.2)',
}} }}
> >
Nothing to display {t('group.message.generic.no_display', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
@ -538,23 +560,25 @@ export const ListOfGroupPromotions = () => {
ref={rowVirtualizer.measureElement} //measure dynamic row height ref={rowVirtualizer.measureElement} //measure dynamic row height
key={promotion?.identifier} key={promotion?.identifier}
style={{ style={{
position: 'absolute',
top: 0,
left: '50%', // Move to the center horizontally
transform: `translateY(${virtualRow.start}px) translateX(-50%)`, // Adjust for centering
width: '100%', // Control width (90% of the parent)
padding: '10px 0',
display: 'flex',
alignItems: 'center', alignItems: 'center',
overscrollBehavior: 'none', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '5px', gap: '5px',
left: '50%', // Move to the center horizontally
overscrollBehavior: 'none',
padding: '10px 0',
position: 'absolute',
top: 0,
transform: `translateY(${virtualRow.start}px) translateX(-50%)`, // Adjust for centering
width: '100%', // Control width (90% of the parent)
}} }}
> >
<ErrorBoundary <ErrorBoundary
fallback={ fallback={
<Typography> <Typography>
Error loading content: Invalid Data {t('group:message.generic.invalid_data', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
} }
> >
@ -569,7 +593,7 @@ export const ListOfGroupPromotions = () => {
<Popover <Popover
open={openPopoverIndex === promotion?.groupId} open={openPopoverIndex === promotion?.groupId}
anchorEl={popoverAnchor} anchorEl={popoverAnchor}
onClose={(event, reason) => { onClose={(reason) => {
if (reason === 'backdropClick') { if (reason === 'backdropClick') {
// Prevent closing on backdrop click // Prevent closing on backdrop click
return; return;
@ -604,7 +628,10 @@ export const ListOfGroupPromotions = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
Group name: {` ${promotion?.groupName}`} {t('group:group.name', {
postProcess: 'capitalizeFirst',
})}
: {` ${promotion?.groupName}`}
</Typography> </Typography>
<Typography <Typography
@ -613,8 +640,10 @@ export const ListOfGroupPromotions = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
Number of members:{' '} {t('group:group.member_number', {
{` ${promotion?.memberCount}`} postProcess: 'capitalizeFirst',
})}
: {` ${promotion?.memberCount}`}
</Typography> </Typography>
{promotion?.description && ( {promotion?.description && (
@ -635,9 +664,9 @@ export const ListOfGroupPromotions = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
*This is a closed/private group, so you {t('group:message.generic.closed_group', {
will need to wait until an admin accepts postProcess: 'capitalizeFirst',
your request })}
</Typography> </Typography>
)} )}
@ -658,7 +687,9 @@ export const ListOfGroupPromotions = () => {
variant="contained" variant="contained"
onClick={handlePopoverClose} onClick={handlePopoverClose}
> >
Close {t('core:action.close', {
postProcess: 'capitalizeFirst',
})}
</LoadingButton> </LoadingButton>
<LoadingButton <LoadingButton
@ -672,7 +703,9 @@ export const ListOfGroupPromotions = () => {
) )
} }
> >
Join {t('core:action.join', {
postProcess: 'capitalizeFirst',
})}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Box> </Box>
@ -756,8 +789,12 @@ export const ListOfGroupPromotions = () => {
}} }}
> >
{promotion?.isOpen {promotion?.isOpen
? 'Public group' ? t('group:group.public', {
: 'Private group'} postProcess: 'capitalizeFirst',
})
: t('group:group.private', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
@ -791,7 +828,10 @@ export const ListOfGroupPromotions = () => {
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
> >
Join Group: {` ${promotion?.groupName}`} {t('group:action.join_group', {
postProcess: 'capitalizeFirst',
})}
: {` ${promotion?.groupName}`}
</Button> </Button>
</Box> </Box>
</Box> </Box>
@ -811,90 +851,114 @@ export const ListOfGroupPromotions = () => {
<Spacer height="20px" /> <Spacer height="20px" />
{isShowModal && ( <Dialog
<Dialog open={isShowModal}
open={isShowModal} aria-labelledby="alert-dialog-title"
aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description"
aria-describedby="alert-dialog-description" >
> <DialogTitle id="alert-dialog-title">
<DialogTitle id="alert-dialog-title"> {t('group:action.promote_group', { postProcess: 'capitalizeFirst' })}
{'Promote your group to non-members'} </DialogTitle>
</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Only the latest promotion from the week will be shown for your {t('group:message.generic.latest_promotion', {
group. postProcess: 'capitalizeFirst',
</DialogContentText> })}
<DialogContentText id="alert-dialog-description2"> </DialogContentText>
Max 200 characters. Publish Fee: {fee && fee} {' QORT'}
</DialogContentText> <DialogContentText id="alert-dialog-description2">
<Spacer height="20px" /> {t('group:message.generic.max_chars', {
<Box postProcess: 'capitalizeFirst',
sx={{ })}
display: 'flex', : {fee && fee} {' QORT'}
flexDirection: 'column', </DialogContentText>
gap: '5px',
}} <Spacer height="20px" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: '5px',
}}
>
<Label>
{t('group:action.select_group', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Label>
{t('group:message.generic.admin_only', {
postProcess: 'capitalizeFirst',
})}
</Label>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={selectedGroup}
label="Groups where you are an admin"
onChange={(e) => setSelectedGroup(e.target.value)}
variant="outlined"
> >
<Label>Select a group</Label> {myGroupsWhereIAmAdmin?.map((group) => {
<Label>Only groups where you are an admin will be shown</Label> return (
<Select <MenuItem key={group?.groupId} value={group?.groupId}>
labelId="demo-simple-select-label" {group?.groupName}
id="demo-simple-select" </MenuItem>
value={selectedGroup} );
label="Groups where you are an admin" })}
onChange={(e) => setSelectedGroup(e.target.value)} </Select>
variant="outlined" </Box>
>
{myGroupsWhereIAmAdmin?.map((group) => { <Spacer height="20px" />
return (
<MenuItem key={group?.groupId} value={group?.groupId}> <TextField
{group?.groupName} label="Promotion text"
</MenuItem> variant="filled"
); fullWidth
})} value={text}
</Select> onChange={(e) => setText(e.target.value)}
</Box> inputProps={{
<Spacer height="20px" /> maxLength: 200,
<TextField }}
label="Promotion text" multiline={true}
variant="filled" sx={{
fullWidth '& .MuiFormLabel-root': {
value={text} color: theme.palette.text.primary,
onChange={(e) => setText(e.target.value)} },
inputProps={{ '& .MuiFormLabel-root.Mui-focused': {
maxLength: 200, color: theme.palette.text.primary,
}} },
multiline={true} }}
sx={{ />
'& .MuiFormLabel-root': { </DialogContent>
color: theme.palette.text.primary,
}, <DialogActions>
'& .MuiFormLabel-root.Mui-focused': { <Button
color: theme.palette.text.primary, disabled={isLoadingPublish}
}, variant="contained"
}} onClick={() => setIsShowModal(false)}
/> >
</DialogContent> {t('core:action.close', {
<DialogActions> postProcess: 'capitalizeFirst',
<Button })}
disabled={isLoadingPublish} </Button>
variant="contained" <Button
onClick={() => setIsShowModal(false)} disabled={!text.trim() || !selectedGroup || isLoadingPublish}
> variant="contained"
Close onClick={publishPromo}
</Button> autoFocus
<Button >
disabled={!text.trim() || !selectedGroup || isLoadingPublish} {t('core:action.publish', {
variant="contained" postProcess: 'capitalizeFirst',
onClick={publishPromo} })}
autoFocus </Button>
> </DialogActions>
Publish </Dialog>
</Button>
</DialogActions>
</Dialog>
)}
<CustomizedSnackbars <CustomizedSnackbars
open={openSnack} open={openSnack}
setOpen={setOpenSnack} setOpen={setOpenSnack}

View File

@ -94,9 +94,9 @@ export const ListOfInvites = ({
const fee = await getFee('CANCEL_GROUP_INVITE'); const fee = await getFee('CANCEL_GROUP_INVITE');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'CANCEL_GROUP_INVITE', action: 'CANCEL_GROUP_INVITE',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -114,7 +114,7 @@ export const ListOfInvites = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.invitation_cancellation', { message: t('group:message.success.invitation_cancellation', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -136,7 +136,7 @@ export const ListOfInvites = ({
message: message:
error.message || error.message ||
t('core:message.error.generic', { t('core:message.error.generic', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -196,7 +196,7 @@ export const ListOfInvites = ({
onClick={() => handleCancelInvitation(member?.invitee)} onClick={() => handleCancelInvitation(member?.invitee)}
> >
{t('core:action.cancel_invitation', { {t('core:action.cancel_invitation', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</Box> </Box>
@ -229,7 +229,7 @@ export const ListOfInvites = ({
<div> <div>
<p> <p>
{t('group:invitees_list', { {t('group:invitees_list', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</p> </p>
<div <div

View File

@ -97,9 +97,9 @@ export const ListOfJoinRequests = ({
const fee = await getFee('GROUP_INVITE'); const fee = await getFee('GROUP_INVITE');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'GROUP_INVITE', action: 'GROUP_INVITE',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -119,7 +119,7 @@ export const ListOfJoinRequests = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success,group_join', { message: t('group:message.success,group_join', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -130,10 +130,10 @@ export const ListOfJoinRequests = ({
...response, ...response,
type: 'join-request-accept', type: 'join-request-accept',
label: t('group:message.success,invitation_request', { label: t('group:message.success,invitation_request', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
labelDone: t('group:message.success,user_joined', { labelDone: t('group:message.success,user_joined', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
done: false, done: false,
groupId, groupId,
@ -157,7 +157,9 @@ export const ListOfJoinRequests = ({
type: 'error', type: 'error',
message: message:
error?.message || error?.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }), t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -223,7 +225,9 @@ export const ListOfJoinRequests = ({
variant="contained" variant="contained"
onClick={() => handleAcceptJoinRequest(member?.joiner)} onClick={() => handleAcceptJoinRequest(member?.joiner)}
> >
{t('core:action.accept', { postProcess: 'capitalize' })} {t('core:action.accept', {
postProcess: 'capitalizeFirst',
})}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>
@ -252,7 +256,7 @@ export const ListOfJoinRequests = ({
return ( return (
<div> <div>
<p>{t('core:list.join_request', { postProcess: 'capitalize' })}</p> <p>{t('core:list.join_request', { postProcess: 'capitalizeFirst' })}</p>
<div <div
style={{ style={{
position: 'relative', position: 'relative',

View File

@ -59,9 +59,9 @@ const ListOfMembers = ({
try { try {
const fee = await getFee('GROUP_KICK'); const fee = await getFee('GROUP_KICK');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'GROUP_KICK', action: 'GROUP_KICK',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -78,7 +78,7 @@ const ListOfMembers = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.group_kick', { message: t('group:message.success.group_kick', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -98,7 +98,9 @@ const ListOfMembers = ({
type: 'error', type: 'error',
message: message:
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }), t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -115,9 +117,9 @@ const ListOfMembers = ({
const fee = await getFee('GROUP_BAN'); const fee = await getFee('GROUP_BAN');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'GROUP_BAN', action: 'GROUP_BAN',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -136,7 +138,7 @@ const ListOfMembers = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.group_ban', { message: t('group:message.success.group_ban', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -156,7 +158,9 @@ const ListOfMembers = ({
type: 'error', type: 'error',
message: message:
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }), t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -173,9 +177,9 @@ const ListOfMembers = ({
try { try {
const fee = await getFee('ADD_GROUP_ADMIN'); const fee = await getFee('ADD_GROUP_ADMIN');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'ADD_GROUP_ADMIN', action: 'ADD_GROUP_ADMIN',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -191,7 +195,7 @@ const ListOfMembers = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.group_member_admin', { message: t('group:message.success.group_member_admin', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -211,7 +215,9 @@ const ListOfMembers = ({
type: 'error', type: 'error',
message: message:
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }), t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -228,9 +234,9 @@ const ListOfMembers = ({
try { try {
const fee = await getFee('REMOVE_GROUP_ADMIN'); const fee = await getFee('REMOVE_GROUP_ADMIN');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'REMOVE_GROUP_ADMIN', action: 'REMOVE_GROUP_ADMIN',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -246,7 +252,7 @@ const ListOfMembers = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.group_remove_member', { message: t('group:message.success.group_remove_member', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -266,7 +272,9 @@ const ListOfMembers = ({
type: 'error', type: 'error',
message: message:
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }), t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -326,7 +334,7 @@ const ListOfMembers = ({
onClick={() => handleKick(member?.member)} onClick={() => handleKick(member?.member)}
> >
{t('group:action.kick_member', { {t('group:action.kick_member', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
@ -337,7 +345,7 @@ const ListOfMembers = ({
onClick={() => handleBan(member?.member)} onClick={() => handleBan(member?.member)}
> >
{t('group:action.ban', { {t('group:action.ban', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
@ -348,7 +356,7 @@ const ListOfMembers = ({
onClick={() => makeAdmin(member?.member)} onClick={() => makeAdmin(member?.member)}
> >
{t('group:action.make_admin', { {t('group:action.make_admin', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
@ -359,7 +367,7 @@ const ListOfMembers = ({
onClick={() => removeAdmin(member?.member)} onClick={() => removeAdmin(member?.member)}
> >
{t('group:action.remove_admin', { {t('group:action.remove_admin', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</> </>
@ -394,7 +402,7 @@ const ListOfMembers = ({
}} }}
> >
{t('core:admin', { {t('core:admin', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
)} )}
@ -410,7 +418,7 @@ const ListOfMembers = ({
<div> <div>
<p> <p>
{t('core:list.member', { {t('core:list.member', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</p> </p>
<div <div

View File

@ -46,7 +46,9 @@ export const ListOfThreadPostsWatched = () => {
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -85,7 +87,7 @@ export const ListOfThreadPostsWatched = () => {
}} }}
> >
{t('group:thread_posts', { {t('group:thread_posts', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
: :
</Typography> </Typography>
@ -133,7 +135,7 @@ export const ListOfThreadPostsWatched = () => {
}} }}
> >
{t('group:message.generic.no_display', { {t('group:message.generic.no_display', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</Box> </Box>

View File

@ -84,9 +84,9 @@ export const ManageMembers = ({
setIsLoadingLeave(true); setIsLoadingLeave(true);
const fee = await getFee('LEAVE_GROUP'); const fee = await getFee('LEAVE_GROUP');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'LEAVE_GROUP', action: 'LEAVE_GROUP',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -104,11 +104,11 @@ export const ManageMembers = ({
type: 'leave-group', type: 'leave-group',
label: t('group:message.success.group_leave_name', { label: t('group:message.success.group_leave_name', {
group_name: selectedGroup?.groupName, group_name: selectedGroup?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
labelDone: t('group:message.success.group_leave_label', { labelDone: t('group:message.success.group_leave_label', {
group_name: selectedGroup?.groupName, group_name: selectedGroup?.groupName,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
done: false, done: false,
groupId: selectedGroup?.groupId, groupId: selectedGroup?.groupId,
@ -119,7 +119,7 @@ export const ManageMembers = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.group_leave', { message: t('group:message.success.group_leave', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -130,7 +130,9 @@ export const ManageMembers = ({
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -207,7 +209,9 @@ export const ManageMembers = ({
> >
<Toolbar> <Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div"> <Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
{t('group:action.manage_members', { postProcess: 'capitalize' })} {t('group:action.manage_members', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
<IconButton <IconButton
@ -310,18 +314,20 @@ export const ManageMembers = ({
> >
<Box> <Box>
<Typography> <Typography>
{t('group:group.id', { postProcess: 'capitalize' })}:{' '} {t('group:group.id', { postProcess: 'capitalizeFirst' })}:{' '}
{groupInfo?.groupId} {groupInfo?.groupId}
</Typography> </Typography>
<Typography> <Typography>
{t('group:group.name', { postProcess: 'capitalize' })}:{' '} {t('group:group.name', { postProcess: 'capitalizeFirst' })}:{' '}
{groupInfo?.groupName} {groupInfo?.groupName}
</Typography> </Typography>
<Typography> <Typography>
{t('group:group.member_number', { postProcess: 'capitalize' })}:{' '} {t('group:group.member_number', {
{groupInfo?.memberCount} postProcess: 'capitalizeFirst',
})}
: {groupInfo?.memberCount}
</Typography> </Typography>
<ButtonBase <ButtonBase
@ -336,7 +342,7 @@ export const ManageMembers = ({
<InsertLinkIcon /> <InsertLinkIcon />
<Typography> <Typography>
{t('group:join_link', { postProcess: 'capitalize' })} {t('group:join_link', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
</Box> </Box>
@ -351,7 +357,9 @@ export const ManageMembers = ({
variant="contained" variant="contained"
onClick={handleLeaveGroup} onClick={handleLeaveGroup}
> >
{t('group:action.leave_group', { postProcess: 'capitalize' })} {t('group:action.leave_group', {
postProcess: 'capitalizeFirst',
})}
</LoadingButton> </LoadingButton>
)} )}
</Card> </Card>
@ -368,7 +376,9 @@ export const ManageMembers = ({
variant="contained" variant="contained"
onClick={() => getMembersWithNames(selectedGroup?.groupId)} onClick={() => getMembersWithNames(selectedGroup?.groupId)}
> >
{t('group:action.load_members', { postProcess: 'capitalize' })} {t('group:action.load_members', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Spacer height="10px" /> <Spacer height="10px" />
@ -465,7 +475,7 @@ export const ManageMembers = ({
open={isLoadingMembers} open={isLoadingMembers}
info={{ info={{
message: t('group:message.generic.loading_members', { message: t('group:message.generic.loading_members', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}} }}
/> />

View File

@ -92,7 +92,9 @@ export const QMailMessages = ({ userName, userAddress }) => {
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -155,7 +157,7 @@ export const QMailMessages = ({ userName, userAddress }) => {
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
{t('group:latest_mails', { postProcess: 'capitalize' })} {t('group:latest_mails', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
<MarkEmailUnreadIcon <MarkEmailUnreadIcon
@ -226,7 +228,7 @@ export const QMailMessages = ({ userName, userAddress }) => {
}} }}
> >
{t('group:message.generic.no_display', { {t('group:message.generic.no_display', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</Box> </Box>

View File

@ -135,7 +135,9 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -160,7 +162,7 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
<Toolbar> <Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div"> <Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
{t('core:general_settings', { {t('core:general_settings', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
@ -194,7 +196,7 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
<LocalNodeSwitch checked={checked} onChange={handleChange} /> <LocalNodeSwitch checked={checked} onChange={handleChange} />
} }
label={t('group:action.disable_push_notifications', { label={t('group:action.disable_push_notifications', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
/> />
{window?.electronAPI && ( {window?.electronAPI && (
@ -212,7 +214,7 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
/> />
} }
label={t('group:action.enable_dev_mode', { label={t('group:action.enable_dev_mode', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
/> />
)} )}
@ -236,7 +238,7 @@ const ExportPrivateKey = ({ rawWallet }) => {
setInfoSnackCustom({ setInfoSnackCustom({
type: 'info', type: 'info',
message: t('group:message.generic.descrypt_wallet', { message: t('group:message.generic.descrypt_wallet', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
@ -260,10 +262,10 @@ const ExportPrivateKey = ({ rawWallet }) => {
message: error?.message message: error?.message
? t('group:message.error.decrypt_wallet', { ? t('group:message.error.decrypt_wallet', {
errorMessage: error?.message, errorMessage: error?.message,
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
: t('group:message.error.descrypt_wallet', { : t('group:message.error.descrypt_wallet', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
@ -281,7 +283,7 @@ const ExportPrivateKey = ({ rawWallet }) => {
onClick={() => setIsOpen(true)} onClick={() => setIsOpen(true)}
> >
{t('group:action.export_private_key', { {t('group:action.export_private_key', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Button> </Button>
@ -292,7 +294,7 @@ const ExportPrivateKey = ({ rawWallet }) => {
> >
<DialogTitle id="alert-dialog-title"> <DialogTitle id="alert-dialog-title">
{t('group:action.export_password', { {t('group:action.export_password', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</DialogTitle> </DialogTitle>
@ -305,7 +307,7 @@ const ExportPrivateKey = ({ rawWallet }) => {
> >
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
{t('group:message.generic.secure_place', { {t('group:message.generic.secure_place', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</DialogContentText> </DialogContentText>
@ -326,7 +328,7 @@ const ExportPrivateKey = ({ rawWallet }) => {
setInfoSnackCustom({ setInfoSnackCustom({
type: 'success', type: 'success',
message: t('group:message.generic.private_key_copied', { message: t('group:message.generic.private_key_copied', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
@ -334,7 +336,7 @@ const ExportPrivateKey = ({ rawWallet }) => {
}} }}
> >
{t('group:action.copy_private_key', { {t('group:action.copy_private_key', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})}{' '} })}{' '}
<ContentCopyIcon color="primary" /> <ContentCopyIcon color="primary" />
</Button> </Button>
@ -351,13 +353,13 @@ const ExportPrivateKey = ({ rawWallet }) => {
}} }}
> >
{t('group:action.cancel', { {t('group:action.cancel', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Button> </Button>
<Button variant="contained" onClick={exportPrivateKeyFunc}> <Button variant="contained" onClick={exportPrivateKeyFunc}>
{t('group:action.decrypt', { {t('group:action.decrypt', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Button> </Button>
</DialogActions> </DialogActions>

View File

@ -76,9 +76,9 @@ export const ThingsToDoInitial = ({
}} }}
> >
{!isLoaded {!isLoaded
? t('core:loading', { postProcess: 'capitalize' }) ? t('core:loading.generic', { postProcess: 'capitalizeFirst' })
: t('tutorial:initial.getting_started', { : t('tutorial:initial.getting_started', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
@ -122,7 +122,7 @@ export const ThingsToDoInitial = ({
}, },
}} }}
primary={t('tutorial:initial.6_qort', { primary={t('tutorial:initial.6_qort', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
/> />
@ -171,7 +171,7 @@ export const ThingsToDoInitial = ({
}, },
}} }}
primary={t('tutorial:initial.register_name', { primary={t('tutorial:initial.register_name', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
/> />
<ListItemIcon <ListItemIcon

View File

@ -99,9 +99,9 @@ export const UserListOfInvites = ({
const fee = await getFee('JOIN_GROUP'); const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: t('group:question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'JOIN_GROUP', action: 'JOIN_GROUP',
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -130,7 +130,7 @@ export const UserListOfInvites = ({
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('group:message.success.group_join', { message: t('group:message.success.group_join', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -149,7 +149,9 @@ export const UserListOfInvites = ({
type: 'error', type: 'error',
message: message:
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }), t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -203,7 +205,7 @@ export const UserListOfInvites = ({
> >
<Typography> <Typography>
{t('core:action.join', { {t('core:action.join', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})}{' '} })}{' '}
{invite?.groupName} {invite?.groupName}
</Typography> </Typography>
@ -217,7 +219,7 @@ export const UserListOfInvites = ({
} }
> >
{t('group:action.join_group', { {t('group:action.join_group', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</Box> </Box>
@ -265,7 +267,7 @@ export const UserListOfInvites = ({
> >
<p> <p>
{t('core:list.invite', { {t('core:list.invite', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</p> </p>

View File

@ -36,13 +36,15 @@ export const NewUsersCTA = ({ balance }) => {
textAlign: 'center', textAlign: 'center',
}} }}
> >
{t('core:question.new_user', { postProcess: 'capitalize' })} {t('core:message.question.new_user', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
<Spacer height="20px" /> <Spacer height="20px" />
<Typography> <Typography>
{t('core:message_us', { postProcess: 'capitalize' })} {t('core:message_us', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
<Spacer height="20px" /> <Spacer height="20px" />

View File

@ -152,7 +152,7 @@ export const QortPrice = () => {
fontWeight: 'bold', fontWeight: 'bold',
}} }}
> >
{t('core:price', { postProcess: 'capitalize' })} {t('core:price', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
{!ltcPerQort ? ( {!ltcPerQort ? (
@ -184,7 +184,7 @@ export const QortPrice = () => {
fontWeight: 'bold', fontWeight: 'bold',
}} }}
> >
{t('core:supply', { postProcess: 'capitalize' })} {t('core:supply', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
{!supply ? ( {!supply ? (
@ -238,7 +238,7 @@ export const QortPrice = () => {
fontWeight: 'bold', fontWeight: 'bold',
}} }}
> >
{t('core:last_height', { postProcess: 'capitalize' })} {t('core:last_height', { postProcess: 'capitalizeFirst' })}
</Typography> </Typography>
{!lastBlock?.height ? ( {!lastBlock?.height ? (

View File

@ -40,7 +40,7 @@ const LanguageSelector = () => {
<Tooltip <Tooltip
key={currentLang} key={currentLang}
title={t('core:action.change_language', { title={t('core:action.change_language', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
> >
<button <button

View File

@ -65,6 +65,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
const publishAvatar = async () => { const publishAvatar = async () => {
try { try {
// TODO translate
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
if (+balance < +fee.fee) if (+balance < +fee.fee)
throw new Error(`Publishing an Avatar requires ${fee.fee}`); throw new Error(`Publishing an Avatar requires ${fee.fee}`);

View File

@ -21,12 +21,13 @@ import {
subscribeToEvent, subscribeToEvent,
unsubscribeFromEvent, unsubscribeFromEvent,
} from '../../utils/events'; } from '../../utils/events';
import { getFee, getNameOrAddress } from '../../background'; import { getFee } from '../../background';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { FidgetSpinner } from 'react-loader-spinner'; import { FidgetSpinner } from 'react-loader-spinner';
import { useModal } from '../../common/useModal'; import { useModal } from '../../common/useModal';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { memberGroupsAtom, txListAtom } from '../../atoms/global'; import { memberGroupsAtom, txListAtom } from '../../atoms/global';
import { useTranslation } from 'react-i18next';
export const Minting = ({ setIsOpenMinting, myAddress, show }) => { export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
const setTxList = useSetAtom(txListAtom); const setTxList = useSetAtom(txListAtom);
@ -44,7 +45,7 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
const { show: showKey, message } = useModal(); const { show: showKey, message } = useModal();
const { isShow: isShowNext, onOk, show: showNext } = useModal(); const { isShow: isShowNext, onOk, show: showNext } = useModal();
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'auth', 'group']);
const [info, setInfo] = useState(null); const [info, setInfo] = useState(null);
const [names, setNames] = useState({}); const [names, setNames] = useState({});
const [accountInfos, setAccountInfos] = useState({}); const [accountInfos, setAccountInfos] = useState({});
@ -223,13 +224,23 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
rej({ message: response.error }); rej({ message: response.error });
}) })
.catch((error) => { .catch((error) => {
rej({ message: error.message || 'An error occurred' }); rej({
message:
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
});
}); });
}); });
} catch (error) { } catch (error) {
setInfo({ setInfo({
type: 'error', type: 'error',
message: error?.message || 'Unable to add minting account', message:
error?.message ||
t('core:message.error.minting_account_add', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
} finally { } finally {
@ -263,13 +274,23 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
rej({ message: response.error }); rej({ message: response.error });
}) })
.catch((error) => { .catch((error) => {
rej({ message: error.message || 'An error occurred' }); rej({
message:
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
});
}); });
}); });
} catch (error) { } catch (error) {
setInfo({ setInfo({
type: 'error', type: 'error',
message: error?.message || 'Unable to remove minting account', message:
error?.message ||
t('core:message.error.minting_account_remove', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
} finally { } finally {
@ -278,9 +299,13 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
}, []); }, []);
const createRewardShare = useCallback(async (publicKey, recipient) => { const createRewardShare = useCallback(async (publicKey, recipient) => {
const fee = await getFee('REWARD_SHARE'); // TODO translate const fee = await getFee('REWARD_SHARE');
await show({ await show({
message: 'Would you like to perform an REWARD_SHARE transaction?', message: t('core:message.question.perform_transaction', {
// TODO move from group into core namespace
action: 'REWARD_SHARE',
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
return await new Promise((res, rej) => { return await new Promise((res, rej) => {
@ -295,8 +320,12 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
recipient, recipient,
...response, ...response,
type: 'add-rewardShare', type: 'add-rewardShare',
label: `Add rewardshare: awaiting confirmation`, label: t('group:message.success.rewardshare_add', {
labelDone: `Add rewardshare: success!`, postProcess: 'capitalizeFirst',
}),
labelDone: t('group:message.success.rewardshare_add_label', {
postProcess: 'capitalizeFirst',
}),
done: false, done: false,
}, },
...prev, ...prev,
@ -307,7 +336,13 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
rej({ message: response.error }); rej({ message: response.error });
}) })
.catch((error) => { .catch((error) => {
rej({ message: error.message || 'An error occurred' }); rej({
message:
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
});
}); });
}); });
}, []); }, []);
@ -326,7 +361,13 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
rej({ message: response.error }); rej({ message: response.error });
}) })
.catch((error) => { .catch((error) => {
rej({ message: error.message || 'An error occurred' }); rej({
message:
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
});
}); });
}); });
}, []); }, []);
@ -350,7 +391,11 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
await sleep(pollingInterval); // Wait before the next poll await sleep(pollingInterval); // Wait before the next poll
} }
throw new Error('Timeout waiting for reward share confirmation'); throw new Error(
t('group:message.error.timeout_reward', {
postProcess: 'capitalizeFirst',
})
);
}; };
const startMinting = async () => { const startMinting = async () => {
@ -382,7 +427,11 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
setShowWaitDialog(false); setShowWaitDialog(false);
setInfo({ setInfo({
type: 'error', type: 'error',
message: error?.message || 'Unable to start minting', message:
error?.message ||
t('group:message.error.unable_minting', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
} finally { } finally {
@ -420,8 +469,12 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
...rewardShare, ...rewardShare,
...response, ...response,
type: 'remove-rewardShare', type: 'remove-rewardShare',
label: `Remove rewardshare: awaiting confirmation`, label: t('group:message.success.rewardshare_remove', {
labelDone: `Remove rewardshare: success!`, postProcess: 'capitalizeFirst',
}),
labelDone: t('group:message.success.rewardshare_remove_label', {
postProcess: 'capitalizeFirst',
}),
done: false, done: false,
}, },
...prev, ...prev,
@ -431,59 +484,67 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
rej({ message: response.error }); rej({ message: response.error });
}) })
.catch((error) => { .catch((error) => {
rej({ message: error.message || 'An error occurred' }); rej({
message:
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
});
}); });
}); });
}, []); }, []);
const handleRemoveRewardShare = async (rewardShare) => { // TODO unused functions. Remove??
try {
setIsLoading(true);
const privateRewardShare = await removeRewardShare(rewardShare); // const handleRemoveRewardShare = async (rewardShare) => {
} catch (error) { // try {
setInfo({ // setIsLoading(true);
type: 'error',
message: error?.message || 'Unable to remove reward share',
});
setOpenSnack(true);
} finally {
setIsLoading(false);
}
};
const createRewardShareForPotentialMinter = async (receiver) => { // const privateRewardShare = await removeRewardShare(rewardShare);
try { // } catch (error) {
setIsLoading(true); // setInfo({
const confirmReceiver = await getNameOrAddress(receiver); // type: 'error',
if (confirmReceiver.error) // message: error?.message || 'Unable to remove reward share',
throw new Error('Invalid receiver address or name'); // });
const isInMinterGroup = await checkIfMinterGroup(confirmReceiver); // setOpenSnack(true);
if (!isInMinterGroup) throw new Error('Account not in Minter Group'); // } finally {
const publicKey = await getPublicKeyFromAddress(confirmReceiver); // setIsLoading(false);
const findRewardShare = rewardShares?.find( // }
(item) => // };
item?.recipient === confirmReceiver &&
item?.mintingAccount === myAddress // const createRewardShareForPotentialMinter = async (receiver) => {
); // try {
if (findRewardShare) { // setIsLoading(true);
const privateRewardShare = await getRewardSharePrivateKey(publicKey); // const confirmReceiver = await getNameOrAddress(receiver);
setRewardsharekey(privateRewardShare); // if (confirmReceiver.error)
} else { // throw new Error('Invalid receiver address or name');
await createRewardShare(publicKey, confirmReceiver); // const isInMinterGroup = await checkIfMinterGroup(confirmReceiver);
const privateRewardShare = await getRewardSharePrivateKey(publicKey); // if (!isInMinterGroup) throw new Error('Account not in Minter Group');
setRewardsharekey(privateRewardShare); // const publicKey = await getPublicKeyFromAddress(confirmReceiver);
} // const findRewardShare = rewardShares?.find(
} catch (error) { // (item) =>
setInfo({ // item?.recipient === confirmReceiver &&
type: 'error', // item?.mintingAccount === myAddress
message: error?.message || 'Unable to create reward share', // );
}); // if (findRewardShare) {
setOpenSnack(true); // const privateRewardShare = await getRewardSharePrivateKey(publicKey);
} finally { // setRewardsharekey(privateRewardShare);
setIsLoading(false); // } else {
} // await createRewardShare(publicKey, confirmReceiver);
}; // const privateRewardShare = await getRewardSharePrivateKey(publicKey);
// setRewardsharekey(privateRewardShare);
// }
// } catch (error) {
// setInfo({
// type: 'error',
// message: error?.message || 'Unable to create reward share',
// });
// setOpenSnack(true);
// } finally {
// setIsLoading(false);
// }
// };
useEffect(() => { useEffect(() => {
getNodeInfos(); getNodeInfos();
@ -558,7 +619,12 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
}, },
}} }}
> >
<DialogTitle id="alert-dialog-title">{'Manage your minting'}</DialogTitle> <DialogTitle id="alert-dialog-title">
{t('group:message.generic.manage_minting', {
postProcess: 'capitalizeFirst',
})}
</DialogTitle>
<IconButton <IconButton
sx={{ sx={{
position: 'absolute', position: 'absolute',
@ -606,19 +672,37 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
padding: '10px', padding: '10px',
}} }}
> >
<Typography>Account: {handleNames(accountInfo?.address)}</Typography>
<Typography>Level: {accountInfo?.level}</Typography>
<Typography> <Typography>
blocks remaining until next level: {_levelUpBlocks()} {t('auth:account.account_one', {
postProcess: 'capitalizeFirst',
})}
: {handleNames(accountInfo?.address)}
</Typography> </Typography>
<Typography> <Typography>
This node is minting: {nodeInfos?.isMintingPossible?.toString()} {t('core:level', {
postProcess: 'capitalizeFirst',
})}
: {accountInfo?.level}
</Typography>
<Typography>
{t('group:message.generic.next_level', {
postProcess: 'capitalizeFirst',
})}{' '}
{_levelUpBlocks()}
</Typography>
<Typography>
{t('group:message.generic.node_minting', {
postProcess: 'capitalizeFirst',
})}{' '}
{nodeInfos?.isMintingPossible?.toString()}
</Typography> </Typography>
</Card> </Card>
<Spacer height="10px" /> <Spacer height="10px" />
{isPartOfMintingGroup && !accountIsMinting && ( {isPartOfMintingGroup && !accountIsMinting && (
<Box <Box
sx={{ sx={{
@ -650,19 +734,29 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
}} }}
variant="contained" variant="contained"
> >
Start minting {t('core:action.start_minting', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
{mintingAccounts?.length > 1 && ( {mintingAccounts?.length > 1 && (
<Typography> <Typography>
Only 2 minting keys are allowed per node. Please remove one if {t('group:message.generic.minting_keys_per_node', {
you would like to mint with this account. postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}
</Box> </Box>
)} )}
<Spacer height="10px" /> <Spacer height="10px" />
{mintingAccounts?.length > 0 && ( {mintingAccounts?.length > 0 && (
<Typography>Node's minting accounts</Typography> <Typography>
{t('group:message.generic.node_minting_account', {
postProcess: 'capitalizeFirst',
})}
</Typography>
)} )}
<Card <Card
sx={{ sx={{
@ -679,12 +773,15 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
}} }}
> >
<Typography> <Typography>
You currently have a minting key for this account attached to {t('group:message.generic.node_minting_key', {
this node postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
<Spacer height="10px" /> <Spacer height="10px" />
{mintingAccounts?.map((acct) => ( {mintingAccounts?.map((acct) => (
<Box <Box
key={acct?.mintingAccount} key={acct?.mintingAccount}
@ -695,8 +792,12 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
}} }}
> >
<Typography> <Typography>
Minting account: {handleNames(acct?.mintingAccount)} {t('group:message.generic.minting_account', {
postProcess: 'capitalizeFirst',
})}{' '}
{handleNames(acct?.mintingAccount)}
</Typography> </Typography>
<Button <Button
size="small" size="small"
sx={{ sx={{
@ -717,7 +818,9 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
}} }}
variant="contained" variant="contained"
> >
Remove minting account {t('group:action.remove_minting_account', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
<Divider /> <Divider />
@ -728,13 +831,15 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
{mintingAccounts?.length > 1 && ( {mintingAccounts?.length > 1 && (
<Typography> <Typography>
Only 2 minting keys are allowed per node. Please remove one if you {t('group:message.generic.minting_keys_per_node_different', {
would like to add a different account. postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}
</Card> </Card>
<Spacer height="20px" /> <Spacer height="20px" />
{!isPartOfMintingGroup && ( {!isPartOfMintingGroup && (
<Card <Card
sx={{ sx={{
@ -752,12 +857,19 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
}} }}
> >
<Typography> <Typography>
You are currently not part of the MINTER group {t('group:message.generic.minter_group', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
<Typography> <Typography>
Visit the Q-Mintership app to apply to be a minter {t('group:message.generic.mintership_app', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
<Spacer height="10px" /> <Spacer height="10px" />
<Button <Button
size="small" size="small"
sx={{ sx={{
@ -781,7 +893,9 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
}} }}
variant="contained" variant="contained"
> >
Visit Q-Mintership {t('group:action.visit_q_mintership', {
postProcess: 'capitalizeFirst',
})}
</Button> </Button>
</Box> </Box>
</Card> </Card>
@ -800,13 +914,16 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
<DialogContent> <DialogContent>
{!isShowNext && ( {!isShowNext && (
<Typography> <Typography>
Confirming creation of rewardshare on chain. Please be {t('group:message.success.rewardshare_creation', {
patient, this could take up to 90 seconds. postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}
{isShowNext && ( {isShowNext && (
<Typography> <Typography>
Rewardshare confirmed. Please click Next. {t('group:message.success.rewardshare_confirmed', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
)} )}
</DialogContent> </DialogContent>
@ -818,21 +935,23 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => {
onClick={onOk} onClick={onOk}
autoFocus autoFocus
> >
Next {t('core:page.next', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
)} )}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
// disabled={isLoadingPublish} // disabled={isLoadingPublish}
variant="contained" variant="contained"
onClick={() => setIsOpenMinting(false)} onClick={() => setIsOpenMinting(false)}
> >
Close {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</DialogActions> </DialogActions>
<Snackbar <Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={openSnack} open={openSnack}

View File

@ -65,8 +65,8 @@ export const QMailStatus = () => {
textTransform: 'uppercase', textTransform: 'uppercase',
}} }}
> >
{t('core:q_mail', { {t('core:q_apps.q_mail', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</span> </span>
} }

View File

@ -37,7 +37,8 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
setSendPaymentError('Please enter your wallet password'); setSendPaymentError('Please enter your wallet password');
return; return;
} }
const fee = await getFee('PAYMENT');
const fee = await getFee('PAYMENT'); // TODO translate
await show({ await show({
message: `Would you like to transfer ${Number(paymentAmount)} QORT?`, message: `Would you like to transfer ${Number(paymentAmount)} QORT?`,
@ -148,7 +149,7 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
<Spacer height="6px" /> <Spacer height="6px" />
<CustomLabel htmlFor="standard-adornment-password"> <CustomLabel htmlFor="standard-adornment-password">
Confirm Wallet Password Confirm wallet password
</CustomLabel> </CustomLabel>
<Spacer height="5px" /> <Spacer height="5px" />

View File

@ -25,6 +25,7 @@ import CheckIcon from '@mui/icons-material/Check';
import ErrorIcon from '@mui/icons-material/Error'; import ErrorIcon from '@mui/icons-material/Error';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { txListAtom } from '../atoms/global'; import { txListAtom } from '../atoms/global';
import { useTranslation } from 'react-i18next';
enum Availability { enum Availability {
NULL = 'null', NULL = 'null',
@ -50,6 +51,7 @@ export const RegisterName = ({
); );
const [nameFee, setNameFee] = useState(null); const [nameFee, setNameFee] = useState(null);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'auth', 'group']);
const checkIfNameExisits = async (name) => { const checkIfNameExisits = async (name) => {
if (!name?.trim()) { if (!name?.trim()) {
setIsNameAvailable(Availability.NULL); setIsNameAvailable(Availability.NULL);
@ -110,12 +112,24 @@ export const RegisterName = ({
const registerName = async () => { const registerName = async () => {
try { try {
if (!userInfo?.address) throw new Error('Your address was not found'); if (!userInfo?.address)
if (!registerNameValue) throw new Error('Enter a name'); throw new Error(
t('core:message.error.address_not_found', {
postProcess: 'capitalizeFirst',
})
);
if (!registerNameValue)
throw new Error(
t('core:action.enter_name', {
postProcess: 'capitalizeFirst',
})
);
const fee = await getFee('REGISTER_NAME'); const fee = await getFee('REGISTER_NAME');
await show({ await show({
message: 'Would you like to register this name?', message: t('core:message.question.register_name', {
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingRegisterName(true); setIsLoadingRegisterName(true);
@ -130,8 +144,9 @@ export const RegisterName = ({
setIsLoadingRegisterName(false); setIsLoadingRegisterName(false);
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.registered_name', {
'Successfully registered. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalizeFirst',
}),
}); });
setIsOpen(false); setIsOpen(false);
setRegisterNameValue(''); setRegisterNameValue('');
@ -140,8 +155,15 @@ export const RegisterName = ({
{ {
...response, ...response,
type: 'register-name', type: 'register-name',
label: `Registered name: awaiting confirmation. This may take a couple minutes.`, label: t('group:message.success.registered_name_label', {
labelDone: `Registered name: success!`, postProcess: 'capitalizeFirst',
}),
labelDone: t(
'group:message.success.registered_name_success',
{
postProcess: 'capitalizeFirst',
}
),
done: false, done: false,
}, },
...prev.filter((item) => !item.done), ...prev.filter((item) => !item.done),
@ -158,7 +180,11 @@ export const RegisterName = ({
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -199,7 +225,9 @@ export const RegisterName = ({
width: '400px', width: '400px',
}} }}
> >
<Label>Choose a name</Label> // TODO: translate <Label>
{t('core:action.choose_name', { postProcess: 'capitalizeFirst' })}
</Label>
<TextField <TextField
autoComplete="off" autoComplete="off"
autoFocus autoFocus
@ -210,6 +238,7 @@ export const RegisterName = ({
{(!balance || (nameFee && balance && balance < nameFee)) && ( {(!balance || (nameFee && balance && balance < nameFee)) && (
<> <>
<Spacer height="10px" /> <Spacer height="10px" />
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -223,15 +252,20 @@ export const RegisterName = ({
}} }}
/> />
<Typography> <Typography>
Your balance is {balance ?? 0} QORT. A name registration {t('core:message.generic.name_registration', {
requires a {nameFee} QORT fee balance: balance ?? 0,
fee: { nameFee },
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
</Box> </Box>
<Spacer height="10px" /> <Spacer height="10px" />
</> </>
)} )}
<Spacer height="5px" /> <Spacer height="5px" />
{isNameAvailable === Availability.AVAILABLE && ( {isNameAvailable === Availability.AVAILABLE && (
<Box <Box
sx={{ sx={{
@ -245,9 +279,15 @@ export const RegisterName = ({
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
/> />
<Typography>{registerNameValue} is available</Typography> <Typography>
{t('core:message.generic.name_available', {
name: registerNameValue,
postProcess: 'capitalizeFirst',
})}
</Typography>
</Box> </Box>
)} )}
{isNameAvailable === Availability.NOT_AVAILABLE && ( {isNameAvailable === Availability.NOT_AVAILABLE && (
<Box <Box
sx={{ sx={{
@ -261,9 +301,15 @@ export const RegisterName = ({
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
/> />
<Typography>{registerNameValue} is unavailable</Typography> <Typography>
{t('core:message.generic.name_unavailable', {
name: registerNameValue,
postProcess: 'capitalizeFirst',
})}
</Typography>
</Box> </Box>
)} )}
{isNameAvailable === Availability.LOADING && ( {isNameAvailable === Availability.LOADING && (
<Box <Box
sx={{ sx={{
@ -273,17 +319,27 @@ export const RegisterName = ({
}} }}
> >
<BarSpinner width="16px" color={theme.palette.text.primary} /> <BarSpinner width="16px" color={theme.palette.text.primary} />
<Typography>Checking if name already existis</Typography>
<Typography>
{t('core:message.generic.name_checking', {
postProcess: 'capitalizeFirst',
})}
</Typography>
</Box> </Box>
)} )}
<Spacer height="25px" /> <Spacer height="25px" />
<Typography <Typography
sx={{ sx={{
textDecoration: 'underline', textDecoration: 'underline',
}} }}
> >
Benefits of a name {t('core:message.generic.name_benefits', {
postProcess: 'capitalizeFirst',
})}
</Typography> </Typography>
<List <List
sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }} sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}
aria-label="contacts" aria-label="contacts"
@ -296,7 +352,11 @@ export const RegisterName = ({
}} }}
/> />
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Publish data to Qortal: anything from apps to videos. Fully decentralized!" /> <ListItemText
primary={t('core:message.generic.publish_data', {
postProcess: 'capitalizeFirst',
})}
/>
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
@ -307,7 +367,11 @@ export const RegisterName = ({
}} }}
/> />
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Secure ownership of data published by your name. You can even sell your name, along with your data to a third party." /> <ListItemText
primary={t('core:message.generic.secure_ownership', {
postProcess: 'capitalizeFirst',
})}
/>
</ListItem> </ListItem>
</List> </List>
</Box> </Box>
@ -322,7 +386,7 @@ export const RegisterName = ({
setRegisterNameValue(''); setRegisterNameValue('');
}} }}
> >
Close {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
<Button <Button
@ -337,7 +401,7 @@ export const RegisterName = ({
onClick={registerName} onClick={registerName}
autoFocus autoFocus
> >
Register Name {t('core:action.register_name', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -155,7 +155,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
message: t('core:save.publish_qnd', { postProcess: 'capitalize' }), message: t('core:message.generic.publish_qnd', {
postProcess: 'capitalizeFirst',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
const response = await new Promise((res, rej) => { const response = await new Promise((res, rej) => {
@ -176,7 +178,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', {
postProcess: 'capitalizeFirst',
})
); );
}); });
}); });
@ -185,8 +189,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
setSettingsQdnLastUpdated(Date.now()); setSettingsQdnLastUpdated(Date.now());
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('core:message.success.publish_qdn', { message: t('core:message.success.published_qdn', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -199,7 +203,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
message: message:
error?.message || error?.message ||
t('core:message.error.save_qdn', { t('core:message.error.save_qdn', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
}); });
setOpenSnack(true); setOpenSnack(true);
@ -232,8 +236,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
{isDesktop ? ( {isDesktop ? (
<IconWrapper <IconWrapper
disableWidth={disableWidth} disableWidth={disableWidth}
label={t('core:save_options.save', { label={t('core:action.save', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
selected={false} selected={false}
color={ color={
@ -303,8 +307,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
fontSize: '14px', fontSize: '14px',
}} }}
> >
{t('core:save_options.settings', { {t('core:message.generic.settings', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography>{' '} </Typography>{' '}
<Spacer height="40px" /> <Spacer height="40px" />
@ -332,8 +336,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}, },
}} }}
> >
{t('core:save_options.qdn', { {t('core:message.generic.qdn', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Button> </Button>
</Box> </Box>
@ -363,8 +367,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
fontSize: '14px', fontSize: '14px',
}} }}
> >
{t('core:save_options.register_name', { {t('core:message.generic.register_name', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</Box> </Box>
@ -384,8 +388,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
fontSize: '14px', fontSize: '14px',
}} }}
> >
{t('core:save_options.unsaved_changes', { {t('core:message.generic.unsaved_changes', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
@ -408,8 +412,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
onClick={saveToQdn} onClick={saveToQdn}
variant="contained" variant="contained"
> >
{t('core:save_options.save_qdn', { {t('core:action.save_qdn', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
<Spacer height="20px" /> <Spacer height="20px" />
@ -421,8 +425,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
fontSize: '14px', fontSize: '14px',
}} }}
> >
{t('core:save_options.reset_qdn', { {t('core:message.question.reset_qdn', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
<Spacer height="10px" /> <Spacer height="10px" />
@ -443,8 +447,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}, },
}} }}
> >
{t('core:save_options.revert_qdn', { {t('core:message.generic.revert_qdn', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</> </>
@ -458,8 +462,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}} }}
> >
{' '} {' '}
{t('core:save_options.reset_pinned', { {t('core:message.question.reset_pinned', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
<Spacer height="10px" /> <Spacer height="10px" />
@ -468,8 +472,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
onClick={revertChanges} onClick={revertChanges}
variant="contained" variant="contained"
> >
{t('core:save_options.revert_default', { {t('core:message.generic.revert_default', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</> </>
@ -492,8 +496,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
fontSize: '14px', fontSize: '14px',
}} }}
> >
{t('core:save_options.overwrite_changes', { {t('core:message.question.overwrite_changes', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
<Spacer height="10px" /> <Spacer height="10px" />
@ -514,8 +518,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}, },
}} }}
> >
{t('core:save_options.overwrite_qdn', { {t('core:message.generic.overwrite_qdn', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</LoadingButton> </LoadingButton>
</Box> </Box>
@ -534,8 +538,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
fontSize: '14px', fontSize: '14px',
}} }}
> >
{t('core:save_options.no_pinned_changes', { {t('core:message.generic.no_pinned_changes', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</Typography> </Typography>
</Box> </Box>
@ -592,7 +596,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}} }}
> >
{t('core:action.import', { {t('core:action.import', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</ButtonBase> </ButtonBase>
@ -617,7 +621,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}} }}
> >
{t('core:action.export', { {t('core:action.export', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
})} })}
</ButtonBase> </ButtonBase>
</Box> </Box>

View File

@ -1,21 +1,17 @@
import * as React from 'react'; import Snackbar from '@mui/material/Snackbar';
import Button from '@mui/material/Button';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
export const LoadingSnackbar = ({open, info}) => { export const LoadingSnackbar = ({ open, info }) => {
return ( return (
<div> <div>
<Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={open}> <Snackbar
<Alert anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
severity="info" open={open}
variant="filled" >
sx={{ width: '100%' }} <Alert severity="info" variant="filled" sx={{ width: '100%' }}>
>
{info?.message} {info?.message}
</Alert> </Alert>
</Snackbar> </Snackbar>
</div> </div>
); );
} };

View File

@ -1,4 +1,3 @@
import * as React from 'react';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar'; import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';

View File

@ -198,9 +198,11 @@ export const TaskManager = ({ getUserInfo }) => {
/> />
)} )}
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Ongoing Transactions" /> <ListItemText primary="Ongoing Transactions" />
{open ? <ExpandLess /> : <ExpandMore />} {open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton> </ListItemButton>
<Collapse in={open} timeout="auto" unmountOnExit> <Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding> <List component="div" disablePadding>
{txList.map((item) => ( {txList.map((item) => (

View File

@ -23,10 +23,10 @@ const ThemeSelector = () => {
title={ title={
themeMode === 'dark' themeMode === 'dark'
? t('core:theme.light', { ? t('core:theme.light', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
: t('core:theme.light', { : t('core:theme.light', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}) })
} }
> >

View File

@ -91,7 +91,7 @@ export const Tutorials = () => {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button variant="contained" onClick={handleClose}> <Button variant="contained" onClick={handleClose}>
{t('core:action.close', { postProcess: 'capitalize' })} {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@ -138,7 +138,7 @@ export const Tutorials = () => {
<DialogActions> <DialogActions>
<Button variant="contained" onClick={handleClose}> <Button variant="contained" onClick={handleClose}>
{t('core:action.close', { postProcess: 'capitalize' })} {t('core:action.close', { postProcess: 'capitalizeFirst' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -108,7 +108,7 @@ export const useHandleTutorials = () => {
multi: [ multi: [
{ {
title: t('tutorial:1_getting_started', { title: t('tutorial:1_getting_started', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
resource: { resource: {
name: 'a-test', name: 'a-test',
@ -119,7 +119,7 @@ export const useHandleTutorials = () => {
}, },
{ {
title: t('tutorial:2_overview', { title: t('tutorial:2_overview', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
resource: { resource: {
name: 'a-test', name: 'a-test',
@ -130,7 +130,7 @@ export const useHandleTutorials = () => {
}, },
{ {
title: t('tutorial:3_groups', { title: t('tutorial:3_groups', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
resource: { resource: {
name: 'a-test', name: 'a-test',
@ -141,7 +141,7 @@ export const useHandleTutorials = () => {
}, },
{ {
title: t('tutorial:4_obtain_qort', { title: t('tutorial:4_obtain_qort', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
resource: { resource: {
name: 'a-test', name: 'a-test',
@ -163,7 +163,7 @@ export const useHandleTutorials = () => {
multi: [ multi: [
{ {
title: t('tutorial:apps.dashboard', { title: t('tutorial:apps.dashboard', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
resource: { resource: {
name: 'a-test', name: 'a-test',
@ -174,7 +174,7 @@ export const useHandleTutorials = () => {
}, },
{ {
title: t('tutorial:apps.navigation', { title: t('tutorial:apps.navigation', {
postProcess: 'capitalize', postProcess: 'capitalizeFirst',
}), }),
resource: { resource: {
name: 'a-test', name: 'a-test',

View File

@ -1,175 +1,186 @@
// Qortal TX types // Qortal TX types
const TX_TYPES = { const TX_TYPES = {
1: "Genesis", 1: 'Genesis',
2: "Payment", 2: 'Payment',
3: "Name registration", 3: 'Name registration',
4: "Name update", 4: 'Name update',
5: "Sell name", 5: 'Sell name',
6: "Cancel sell name", 6: 'Cancel sell name',
7: "Buy name", 7: 'Buy name',
8: "Create poll", 8: 'Create poll',
9: "Vote in poll", 9: 'Vote in poll',
10: "Arbitrary", 10: 'Arbitrary',
11: "Issue asset", 11: 'Issue asset',
12: "Transfer asset", 12: 'Transfer asset',
13: "Create asset order", 13: 'Create asset order',
14: "Cancel asset order", 14: 'Cancel asset order',
15: "Multi-payment transaction", 15: 'Multi-payment transaction',
16: "Deploy AT", 16: 'Deploy AT',
17: "Message", 17: 'Message',
18: "Chat", 18: 'Chat',
19: "Publicize", 19: 'Publicize',
20: "Airdrop", 20: 'Airdrop',
21: "AT", 21: 'AT',
22: "Create group", 22: 'Create group',
23: "Update group", 23: 'Update group',
24: "Add group admin", 24: 'Add group admin',
25: "Remove group admin", 25: 'Remove group admin',
26: "Group ban", 26: 'Group ban',
27: "Cancel group ban", 27: 'Cancel group ban',
28: "Group kick", 28: 'Group kick',
29: "Group invite", 29: 'Group invite',
30: "Cancel group invite", 30: 'Cancel group invite',
31: "Join group", 31: 'Join group',
32: "Leave group", 32: 'Leave group',
33: "Group approval", 33: 'Group approval',
34: "Set group", 34: 'Set group',
35: "Update asset", 35: 'Update asset',
36: "Account flags", 36: 'Account flags',
37: "Enable forging", 37: 'Enable forging',
38: "Reward share", 38: 'Reward share',
39: "Account level", 39: 'Account level',
40: "Transfer privs", 40: 'Transfer privs',
41: "Presence" 41: 'Presence',
} };
// Qortal error codes // Qortal error codes
const ERROR_CODES = { const ERROR_CODES = {
1: "Valid OK", 1: 'Valid OK',
2: "Invalid address", 2: 'Invalid address',
3: "Negative amount", 3: 'Negative amount',
4: "Nagative fee", 4: 'Nagative fee',
5: "No balance", 5: 'No balance',
6: "Invalid reference", 6: 'Invalid reference',
7: "Invalid time length", 7: 'Invalid time length',
8: "Invalid value length", 8: 'Invalid value length',
9: "Name already registered", 9: 'Name already registered',
10: "Name does not exist", 10: 'Name does not exist',
11: "Invalid name owner", 11: 'Invalid name owner',
12: "Name already for sale", 12: 'Name already for sale',
13: "Name not for sale", 13: 'Name not for sale',
14: "Name buyer already owner", 14: 'Name buyer already owner',
15: "Invalid amount", 15: 'Invalid amount',
16: "Invalid seller", 16: 'Invalid seller',
17: "Name not lowercase", 17: 'Name not lowercase',
18: "Invalid description length", 18: 'Invalid description length',
19: "Invalid options length", 19: 'Invalid options length',
20: "Invalid option length", 20: 'Invalid option length',
21: "Duplicate option", 21: 'Duplicate option',
22: "Poll already created", 22: 'Poll already created',
23: "Poll already has votes", 23: 'Poll already has votes',
24: "Poll does not exist", 24: 'Poll does not exist',
25: "Option does not exist", 25: 'Option does not exist',
26: "Already voted for that option", 26: 'Already voted for that option',
27: "Invalid data length", 27: 'Invalid data length',
28: "Invalid quantity", 28: 'Invalid quantity',
29: "Asset does not exist", 29: 'Asset does not exist',
30: "Invalid return", 30: 'Invalid return',
31: "Have equals want", 31: 'Have equals want',
32: "Order does not exist", 32: 'Order does not exist',
33: "Invalid order creator", 33: 'Invalid order creator',
34: "Invalid payments length", 34: 'Invalid payments length',
35: "Negative price", 35: 'Negative price',
36: "Invalid creation bytes", 36: 'Invalid creation bytes',
37: "Invalid tags length", 37: 'Invalid tags length',
38: "Invalid type length", 38: 'Invalid type length',
39: "Invalid AT transaction", 39: 'Invalid AT transaction',
40: "Insufficient fee", 40: 'Insufficient fee',
41: "Asset does not match AT", 41: 'Asset does not match AT',
43: "Asset already exists", 43: 'Asset already exists',
44: "Missing creator", 44: 'Missing creator',
45: "Timestamp too old", 45: 'Timestamp too old',
46: "Timestamp too new", 46: 'Timestamp too new',
47: "Too many unconfirmed", 47: 'Too many unconfirmed',
48: "Group already exists", 48: 'Group already exists',
49: "Group does not exist", 49: 'Group does not exist',
50: "Invalid group owner", 50: 'Invalid group owner',
51: "Already group memeber", 51: 'Already group memeber',
52: "Group owner can not leave", 52: 'Group owner can not leave',
53: "Not group member", 53: 'Not group member',
54: "Already group admin", 54: 'Already group admin',
55: "Not group admin", 55: 'Not group admin',
56: "Invalid lifetime", 56: 'Invalid lifetime',
57: "Invite unknown", 57: 'Invite unknown',
58: "Ban exists", 58: 'Ban exists',
59: "Ban unknown", 59: 'Ban unknown',
60: "Banned from group", 60: 'Banned from group',
61: "Join request", 61: 'Join request',
62: "Invalid group approval threshold", 62: 'Invalid group approval threshold',
63: "Group ID mismatch", 63: 'Group ID mismatch',
64: "Invalid group ID", 64: 'Invalid group ID',
65: "Transaction unknown", 65: 'Transaction unknown',
66: "Transaction already confirmed", 66: 'Transaction already confirmed',
67: "Invalid TX group", 67: 'Invalid TX group',
68: "TX group ID mismatch", 68: 'TX group ID mismatch',
69: "Multiple names forbidden", 69: 'Multiple names forbidden',
70: "Invalid asset owner", 70: 'Invalid asset owner',
71: "AT is finished", 71: 'AT is finished',
72: "No flag permission", 72: 'No flag permission',
73: "Not minting accout", 73: 'Not minting accout',
77: "Invalid rewardshare percent", 77: 'Invalid rewardshare percent',
78: "Public key unknown", 78: 'Public key unknown',
79: "Invalid public key", 79: 'Invalid public key',
80: "AT unknown", 80: 'AT unknown',
81: "AT already exists", 81: 'AT already exists',
82: "Group approval not required", 82: 'Group approval not required',
83: "Group approval decided", 83: 'Group approval decided',
84: "Maximum reward shares", 84: 'Maximum reward shares',
85: "Transaction already exists", 85: 'Transaction already exists',
86: "No blockchain lock", 86: 'No blockchain lock',
87: "Order already closed", 87: 'Order already closed',
88: "Clock not synced", 88: 'Clock not synced',
89: "Asset not spendable", 89: 'Asset not spendable',
90: "Account can not reward share", 90: 'Account can not reward share',
91: "Self share exists", 91: 'Self share exists',
92: "Account already exists", 92: 'Account already exists',
93: "Invalid group block delay", 93: 'Invalid group block delay',
94: "Incorrect nonce", 94: 'Incorrect nonce',
95: "Ivalid timestamp signature", 95: 'Ivalid timestamp signature',
96: "Address blocked", 96: 'Address blocked',
97: "Name Blocked", 97: 'Name Blocked',
98: "Group approval required", 98: 'Group approval required',
99: "Account not transferable", 99: 'Account not transferable',
999: "Ivalid but ok", 999: 'Ivalid but ok',
1000: "Not yet released." 1000: 'Not yet released.',
} };
// Qortal 8 decimals // Qortal 8 decimals
const QORT_DECIMALS = 1e8 const QORT_DECIMALS = 1e8;
// Q for Qortal // Q for Qortal
const ADDRESS_VERSION = 58 const ADDRESS_VERSION = 58;
// Proxy for api calls // Proxy for api calls
const PROXY_URL = "/proxy/" const PROXY_URL = '/proxy/';
// Chat reference timestamp // Chat reference timestamp
const CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP = 1674316800000 const CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP = 1674316800000;
// Dynamic fee timestamp // Dynamic fee timestamp
const DYNAMIC_FEE_TIMESTAMP = 1692118800000 const DYNAMIC_FEE_TIMESTAMP = 1692118800000;
// Used as a salt for all Qora addresses. Salts used for storing your private keys in local storage will be randomly generated // Used as a salt for all Qora addresses. Salts used for storing your private keys in local storage will be randomly generated
const STATIC_SALT = new Uint8Array([54, 190, 201, 206, 65, 29, 123, 129, 147, 231, 180, 166, 171, 45, 95, 165, 78, 200, 208, 194, 44, 207, 221, 146, 45, 238, 68, 68, 69, 102, 62, 6]) const STATIC_SALT = new Uint8Array([
const BCRYPT_ROUNDS = 10 // Remember that the total work spent on key derivation is BCRYPT_ROUNDS * KDF_THREADS 54, 190, 201, 206, 65, 29, 123, 129, 147, 231, 180, 166, 171, 45, 95, 165, 78,
const BCRYPT_VERSION = "2a" 200, 208, 194, 44, 207, 221, 146, 45, 238, 68, 68, 69, 102, 62, 6,
const STATIC_BCRYPT_SALT = `$${BCRYPT_VERSION}$${BCRYPT_ROUNDS}$IxVE941tXVUD4cW0TNVm.O` ]);
const KDF_THREADS = 16 const BCRYPT_ROUNDS = 10; // Remember that the total work spent on key derivation is BCRYPT_ROUNDS * KDF_THREADS
const BCRYPT_VERSION = '2a';
const STATIC_BCRYPT_SALT = `$${BCRYPT_VERSION}$${BCRYPT_ROUNDS}$IxVE941tXVUD4cW0TNVm.O`;
const KDF_THREADS = 16;
export { TX_TYPES, ERROR_CODES, QORT_DECIMALS, PROXY_URL, STATIC_SALT, ADDRESS_VERSION, KDF_THREADS, STATIC_BCRYPT_SALT, CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP, DYNAMIC_FEE_TIMESTAMP } export {
TX_TYPES,
ERROR_CODES,
QORT_DECIMALS,
PROXY_URL,
STATIC_SALT,
ADDRESS_VERSION,
KDF_THREADS,
STATIC_BCRYPT_SALT,
CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP,
DYNAMIC_FEE_TIMESTAMP,
};

View File

@ -7,11 +7,9 @@ import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
export const useHandlePaymentNotification = (address) => { export const useHandlePaymentNotification = (address) => {
const [latestTx, setLatestTx] = useState(null);
const nameAddressOfSender = useRef({}); const nameAddressOfSender = useRef({});
const isFetchingName = useRef({}); const isFetchingName = useRef({});
const [latestTx, setLatestTx] = useState(null);
const [lastEnteredTimestampPayment, setLastEnteredTimestampPayment] = useAtom( const [lastEnteredTimestampPayment, setLastEnteredTimestampPayment] = useAtom(
lastPaymentSeenTimestampAtom lastPaymentSeenTimestampAtom
); );
@ -63,6 +61,7 @@ export const useHandlePaymentNotification = (address) => {
const key = `last-seen-payment-${address}`; const key = `last-seen-payment-${address}`;
const res = await getData<any>(key).catch(() => null); const res = await getData<any>(key).catch(() => null);
if (res) { if (res) {
setLastEnteredTimestampPayment(res); setLastEnteredTimestampPayment(res);
} }
@ -76,6 +75,7 @@ export const useHandlePaymentNotification = (address) => {
const latestTx = responseData.filter( const latestTx = responseData.filter(
(tx) => tx?.creatorAddress !== address && tx?.recipient === address (tx) => tx?.creatorAddress !== address && tx?.recipient === address
)[0]; )[0];
if (!latestTx) { if (!latestTx) {
return; // continue to the next group return; // continue to the next group
} }
@ -128,6 +128,7 @@ export const useHandlePaymentNotification = (address) => {
); );
}; };
}, [setLastEnteredTimestampPaymentEventFunc]); }, [setLastEnteredTimestampPaymentEventFunc]);
return { return {
latestTx, latestTx,
getNameOrAddressOfSenderMiddle, getNameOrAddressOfSenderMiddle,

View File

@ -5,6 +5,7 @@ interface NameListItem {
name: string; name: string;
address: string; address: string;
} }
export const useNameSearch = (value: string, limit = 20) => { export const useNameSearch = (value: string, limit = 20) => {
const [nameList, setNameList] = useState<NameListItem[]>([]); const [nameList, setNameList] = useState<NameListItem[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -48,6 +49,7 @@ export const useNameSearch = (value: string, limit = 20) => {
clearTimeout(handler); clearTimeout(handler);
}; };
}, [value, limit, checkIfNameExisits]); }, [value, limit, checkIfNameExisits]);
return { return {
isLoading, isLoading,
results: nameList, results: nameList,

View File

@ -2,9 +2,15 @@ import i18n from 'i18next';
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector'; import LanguageDetector from 'i18next-browser-languagedetector';
const capitalize = { const capitalizeAll = {
type: 'postProcessor', type: 'postProcessor',
name: 'capitalize', name: 'capitalizeAll',
process: (value: string) => value.toUpperCase(),
};
const capitalizeFirst = {
type: 'postProcessor',
name: 'capitalizeFirst',
process: (value: string) => value.charAt(0).toUpperCase() + value.slice(1), process: (value: string) => value.charAt(0).toUpperCase() + value.slice(1),
}; };
@ -38,7 +44,8 @@ for (const path in modules) {
i18n i18n
.use(initReactI18next) .use(initReactI18next)
.use(LanguageDetector) .use(LanguageDetector)
.use(capitalize as any) .use(capitalizeAll as any)
.use(capitalizeFirst as any)
.init({ .init({
resources, resources,
fallbackLng: 'en', fallbackLng: 'en',

View File

@ -1,8 +1,26 @@
{ {
"account": { "account": {
"your": "ihr Konto", "your": "dein Konto",
"account_many": "Konten", "account_many": "Konten",
"account_one": "Konto" "account_one": "Konto",
"selected": "ausgewähltes Konto"
},
"action": {
"add": {
"account": "Konto hinzufügen",
"seed_phrase": "Seed-Phrase hinzufügen"
},
"authenticate": "authentifizieren",
"create_account": "Konto erstellen",
"create_qortal_account": "erstelle dein Qortal-Konto, indem du unten auf <next>WEITER</next> klickst.",
"choose_password": "neues Passwort wählen",
"download_account": "Konto herunterladen",
"export_seedphrase": "Seedphrase exportieren",
"publish_admin_secret_key": "Admin-Geheimschlüssel veröffentlichen",
"publish_group_secret_key": "Gruppen-Geheimschlüssel veröffentlichen",
"reencrypt_key": "Schlüssel neu verschlüsseln",
"return_to_list": "zur Liste zurückkehren",
"setup_qortal_account": "Qortal-Konto einrichten"
}, },
"advanced_users": "für fortgeschrittene Benutzer", "advanced_users": "für fortgeschrittene Benutzer",
"apikey": { "apikey": {
@ -13,11 +31,33 @@
"key": "API-Schlüssel", "key": "API-Schlüssel",
"select_valid": "gültigen API-Schlüssel auswählen" "select_valid": "gültigen API-Schlüssel auswählen"
}, },
"authenticate": "authentifizieren",
"build_version": "Build-Version", "build_version": "Build-Version",
"create_account": "Konto erstellen", "message": {
"download_account": "Konto herunterladen", "error": {
"keep_secure": "Bewahren Sie Ihre Kontodatei sicher auf", "account_creation": "Konto konnte nicht erstellt werden.",
"field_not_found_json": "{{ field }} nicht im JSON gefunden",
"incorrect_password": "falsches Passwort",
"invalid_secret_key": "Geheimschlüssel ist ungültig",
"unable_reencrypt_secret_key": "Geheimschlüssel konnte nicht neu verschlüsselt werden"
},
"generic": {
"congrats_setup": "Glückwunsch, du bist startklar!",
"no_account": "keine gespeicherten Konten",
"no_minimum_length": "es gibt keine Mindestlängenanforderung",
"no_secret_key_published": "noch kein Geheimschlüssel veröffentlicht",
"fetching_admin_secret_key": "Admin-Geheimschlüssel wird abgerufen",
"fetching_group_secret_key": "Gruppen-Geheimschlüssel wird veröffentlicht",
"last_encryption_date": "letztes Verschlüsselungsdatum: {{ date }} von {{ name }}",
"keep_secure": "halte deine Kontodatei sicher",
"publishing_key": "Hinweis: Nach der Veröffentlichung des Schlüssels dauert es ein paar Minuten, bis er erscheint. Bitte etwas Geduld.",
"seedphrase_notice": "eine <seed>SEEDPHRASE</seed> wurde im Hintergrund zufällig generiert.",
"type_seed": "gib deine Seed-Phrase ein oder füge sie ein",
"your_accounts": "deine gespeicherten Konten"
},
"success": {
"reencrypted_secret_key": "Geheimschlüssel erfolgreich neu verschlüsselt. Die Änderungen werden in ein paar Minuten wirksam. Bitte die Gruppe in 5 Minuten aktualisieren."
}
},
"node": { "node": {
"choose": "benutzerdefinierten Node auswählen", "choose": "benutzerdefinierten Node auswählen",
"custom_many": "benutzerdefinierte Nodes", "custom_many": "benutzerdefinierte Nodes",
@ -26,17 +66,31 @@
"using": "verwende Node", "using": "verwende Node",
"using_public": "öffentlichen Node verwenden" "using_public": "öffentlichen Node verwenden"
}, },
"note": "Hinweis",
"password": "Passwort", "password": "Passwort",
"password_confirmation": "Passwort bestätigen", "password_confirmation": "Passwort bestätigen",
"return_to_list": "zurück zur Liste", "seed": "Seed-Phrase",
"seed_your": "deine Seedphrase",
"tips": {
"additional_wallet": "verwende diese Option, um weitere Qortal-Wallets zu verbinden, die du bereits erstellt hast, um dich später mit ihnen anzumelden. Du benötigst deine Sicherungs-JSON-Datei.",
"digital_id": "deine Wallet ist wie deine digitale ID auf Qortal und dient zur Anmeldung im Qortal-Interface. Sie enthält deine öffentliche Adresse und den Qortal-Namen, den du später auswählst. Jede Transaktion ist mit deiner ID verknüpft. Hier verwaltest du dein QORT und andere handelbare Kryptowährungen auf Qortal.",
"existing_account": "du hast bereits ein Qortal-Konto? Gib hier deine geheime Sicherungsphrase ein, um darauf zuzugreifen. Diese Phrase ist eine Möglichkeit zur Wiederherstellung deines Kontos.",
"key_encrypt_admin": "dieser Schlüssel dient der Verschlüsselung von ADMIN-Inhalten. Nur Admins können diese Inhalte sehen.",
"key_encrypt_group": "dieser Schlüssel dient der Verschlüsselung von GRUPPEN-Inhalten. Aktuell wird nur dieser Schlüssel in der Benutzeroberfläche verwendet. Alle Gruppenmitglieder können die Inhalte sehen.",
"new_account": "ein Konto zu erstellen bedeutet, eine neue Wallet und digitale ID für die Nutzung von Qortal zu generieren. Nach der Kontoerstellung kannst du QORT erhalten, einen Namen und Avatar kaufen, Videos und Blogs veröffentlichen und vieles mehr.",
"new_users": "neue Nutzer starten hier!",
"safe_place": "speichere dein Konto an einem Ort, den du nicht vergisst!",
"view_seedphrase": "wenn du die SEEDPHRASE ANZEIGEN möchtest, klicke auf das Wort 'SEEDPHRASE' in diesem Text. Seedphrases erzeugen den privaten Schlüssel für dein Qortal-Konto. Aus Sicherheitsgründen werden sie standardmäßig NICHT angezeigt.",
"wallet_secure": "halte deine Wallet-Datei sicher."
},
"wallet": { "wallet": {
"password_confirmation": "Wallet-Passwort bestätigen", "password_confirmation": "Wallet-Passwort bestätigen",
"password": "Wallet-Passwort", "password": "Wallet-Passwort",
"keep_password": "aktuelles Passwort beibehalten", "keep_password": "aktuelles Passwort behalten",
"new_password": "neues Passwort", "new_password": "neues Passwort",
"error": { "error": {
"missing_new_password": "bitte neues Passwort eingeben", "missing_new_password": "bitte ein neues Passwort eingeben",
"missing_password": "bitte Passwort eingeben" "missing_password": "bitte dein Passwort eingeben"
} }
}, },
"welcome": "willkommen bei" "welcome": "willkommen bei"

View File

@ -2,7 +2,25 @@
"account": { "account": {
"your": "your account", "your": "your account",
"account_many": "accounts", "account_many": "accounts",
"account_one": "account" "account_one": "account",
"selected": "selected account"
},
"action": {
"add": {
"account": "add account",
"seed_phrase": "add seed-phrase"
},
"authenticate": "authenticate",
"create_account": "create account",
"create_qortal_account": "create your Qortal account by clicking <next>NEXT</next> below.",
"choose_password": "choose new password",
"download_account": "download account",
"export_seedphrase": "export Seedphrase",
"publish_admin_secret_key": "publish admin secret key",
"publish_group_secret_key": "publish group secret key",
"reencrypt_key": "re-encrypt key",
"return_to_list": "return to list",
"setup_qortal_account": "set up your Qortal account"
}, },
"advanced_users": "for advanced users", "advanced_users": "for advanced users",
"apikey": { "apikey": {
@ -13,11 +31,33 @@
"key": "API key", "key": "API key",
"select_valid": "select a valid apikey" "select_valid": "select a valid apikey"
}, },
"authenticate": "authenticate",
"build_version": "build version", "build_version": "build version",
"create_account": "create account", "message": {
"download_account": "download account", "error": {
"keep_secure": "keep your account file secure", "account_creation": "could not create account.",
"field_not_found_json": "{{ field }} not found in JSON",
"incorrect_password": "incorrect password",
"invalid_secret_key": "secretKey is not valid",
"unable_reencrypt_secret_key": "unable to re-encrypt secret key"
},
"generic": {
"congrats_setup": "congrats, youre all set up!",
"no_account": "no accounts saved",
"no_minimum_length": "there is no minimum length requirement",
"no_secret_key_published": "no secret key published yet",
"fetching_admin_secret_key": "fetching Admins secret key",
"fetching_group_secret_key": "fetching Group secret key publishes",
"last_encryption_date": "last encryption date: {{ date }} by {{ name }}",
"keep_secure": "keep your account file secure",
"publishing_key": "reminder: After publishing the key, it will take a couple of minutes for it to appear. Please just wait.",
"seedphrase_notice": "a <seed>SEEDPHRASE</seed> has been randomly generated in the background.",
"type_seed": "type or paste in your seed-phrase",
"your_accounts": "your saved accounts"
},
"success": {
"reencrypted_secret_key": "successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins."
}
},
"node": { "node": {
"choose": "choose custom node", "choose": "choose custom node",
"custom_many": "custom nodes", "custom_many": "custom nodes",
@ -26,13 +66,22 @@
"using": "using node", "using": "using node",
"using_public": "using public node" "using_public": "using public node"
}, },
"note": "note",
"password": "password", "password": "password",
"password_confirmation": "confirm password", "password_confirmation": "confirm password",
"return_to_list": "return to list", "seed": "seed phrase",
"seed_your": "your seedphrase",
"tips": { "tips": {
"additional_wallet": "use this option to connect additional Qortal wallets you've already made, in order to login with them afterwards. You will need access to your backup JSON file in order to do so.",
"digital_id": "your wallet is like your digital ID on Qortal, and is how you will login to the Qortal User Interface. It holds your public address and the Qortal name you will eventually choose. Every transaction you make is linked to your ID, and this is where you manage all your QORT and other tradeable cryptocurrencies on Qortal.", "digital_id": "your wallet is like your digital ID on Qortal, and is how you will login to the Qortal User Interface. It holds your public address and the Qortal name you will eventually choose. Every transaction you make is linked to your ID, and this is where you manage all your QORT and other tradeable cryptocurrencies on Qortal.",
"existing_account": "already have a Qortal account? Enter your secret backup phrase here to access it. This phrase is one of the ways to recover your account.",
"key_encrypt_admin": "this key is to encrypt ADMIN related content. Only admins would see content encrypted with it.",
"key_encrypt_group": "this key is to encrypt GROUP related content. This is the only one used in this UI as of now. All group members will be able to see content encrypted with this key.",
"new_account": "creating an account means creating a new wallet and digital ID to start using Qortal. Once you have made your account, you can start doing things like obtaining some QORT, buying a name and avatar, publishing videos and blogs, and much more.", "new_account": "creating an account means creating a new wallet and digital ID to start using Qortal. Once you have made your account, you can start doing things like obtaining some QORT, buying a name and avatar, publishing videos and blogs, and much more.",
"new_users": "new users start here!" "new_users": "new users start here!",
"safe_place": "save your account in a place where you will remember it!",
"view_seedphrase": "if you wish to VIEW THE SEEDPHRASE, click the word 'SEEDPHRASE' in this text. Seedphrases are used to generate the private key for your Qortal account. For security by default, seedphrases are NOT displayed unless specifically chosen.",
"wallet_secure": "keep your wallet file secure."
}, },
"wallet": { "wallet": {
"password_confirmation": "confirm wallet password", "password_confirmation": "confirm wallet password",

View File

@ -1,41 +1,98 @@
{ {
"action": { "action": {
"add": "add", "add": "add",
"add_custom_framework": "add custom framework",
"add_reaction": "add reaction",
"accept": "accept", "accept": "accept",
"access": "access",
"backup_account": "backup account", "backup_account": "backup account",
"backup_wallet": "backup wallet", "backup_wallet": "backup wallet",
"cancel": "cancel", "cancel": "cancel",
"cancel_invitation": "cancel invitation", "cancel_invitation": "cancel invitation",
"change": "change", "change": "change",
"change_avatar": "change avatar",
"change_file": "change file",
"change_language": "change language", "change_language": "change language",
"choose": "choose", "choose": "choose",
"choose_file": "choose file",
"choose_image": "choose image",
"close": "close", "close": "close",
"close_chat": "close Direct Chat",
"continue": "continue", "continue": "continue",
"continue_logout": "continue to logout", "continue_logout": "continue to logout",
"copy_link": "copy link",
"create_apps": "create apps",
"create_file": "create file",
"create_thread": "create thread", "create_thread": "create thread",
"choose_logo": "choose a logo",
"choose_name": "choose a name",
"decline": "decline", "decline": "decline",
"decrypt": "decrypt", "decrypt": "decrypt",
"disable_enter": "disable enter",
"download": "download",
"edit": "edit", "edit": "edit",
"enter_name": "enter a name",
"export": "export", "export": "export",
"get_qort": "get QORT at Q-Trade",
"hide": "hide",
"import": "import", "import": "import",
"invite": "invite", "invite": "invite",
"join": "join", "join": "join",
"leave_comment": "leave comment",
"load_announcements": "load older announcements",
"login": "login",
"logout": "logout", "logout": "logout",
"new": { "new": {
"post": "new post", "post": "new post",
"thread": "new thread" "thread": "new thread"
}, },
"notify": "notify", "notify": "notify",
"open": "open",
"pin": "pin",
"pin_app": "pin app",
"pin_from_dashboard": "pin from dashboard",
"post": "post", "post": "post",
"post_message": "post message" "post_message": "post message",
"publish": "publish",
"publish_app": "publish your app",
"publish_comment": "publish comment",
"register_name": "register name",
"remove": "remove",
"remove_reaction": "remove reaction",
"return_apps_dashboard": "return to Apps Dashboard",
"save": "save",
"search": "search",
"search_apps": "search for apps",
"select_app_type": "select App Type",
"select_category": "select Category",
"select_name_app": "select Name/App",
"set_avatar": "set avatar",
"start_minting": "start minting",
"start_typing": "start typing here...",
"transfer_qort": "Transfer QORT",
"unpin": "unpin",
"unpin_app": "unpin app",
"unpin_from_dashboard": "unpin from dashboard",
"update": "update",
"update_app": "update your app"
}, },
"admin": "admin", "admin": "admin",
"all": "all",
"api": "API",
"app": "app",
"app_name": "app name",
"app_service_type": "app service type",
"apps_dashboard": "apps Dashboard",
"apps_official": "official Apps",
"category": "category",
"category_other": "categories",
"core": { "core": {
"block_height": "block height", "block_height": "block height",
"information": "core information", "information": "core information",
"peers": "connected peers", "peers": "connected peers",
"version": "core version" "version": "core version"
}, },
"domain": "domain",
"ui": { "ui": {
"version": "UI version" "version": "UI version"
}, },
@ -44,27 +101,123 @@
"one": "one" "one": "one"
}, },
"description": "description", "description": "description",
"devmode_apps": "dev Mode Apps",
"directory": "directory",
"downloading_qdn": "downloading from QDN", "downloading_qdn": "downloading from QDN",
"fee": { "fee": {
"payment": "payment fee", "payment": "payment fee",
"publish": "publish fee" "publish": "publish fee"
}, },
"for": "for",
"general_settings": "general settings", "general_settings": "general settings",
"identifier": "identifier",
"last_height": "last height", "last_height": "last height",
"level": "level",
"library": "library",
"list": { "list": {
"invite": "invite list", "invite": "invite list",
"join_request": "join request list", "join_request": "join request list",
"member": "member list" "member": "member list"
}, },
"loading": "loading...", "loading": {
"loading_posts": "loading posts... please wait.", "announcements": "announcements",
"generic": "loading...",
"chat": "loading chat... please wait.",
"comments": "loading comments... please wait.",
"posts": "loading posts... please wait."
},
"message_us": "please message us on Telegram or Discord if you need 4 QORT to start chatting without any limitations", "message_us": "please message us on Telegram or Discord if you need 4 QORT to start chatting without any limitations",
"message": { "message": {
"error": { "error": {
"address_not_found": "your address was not found",
"app_need_name": "your app needs a name",
"file_too_large": "file {{ filename }} is too large. Max size allowed is {{ size }} MB.",
"generic": "an error occurred", "generic": "an error occurred",
"incorrect_password": "incorrect password", "invalid_signature": "invalid signature",
"missing_field": "missing: {{ field }}", "invalid_zip": "invalid zip",
"save_qdn": "unable to save to QDN" "message_loading": "error loading message.",
"message_size": "your message size is of {{ size }} bytes out of a maximum of {{ maximum }}",
"minting_account_add": "unable to add minting account",
"minting_account_remove": "unable to remove minting account",
"missing_fields": "missing: {{ fields }}",
"navigation_timeout": "navigation timeout",
"network_generic": "network error",
"password_not_matching": "password fields do not match!",
"password_wrong": "unable to authenticate. Wrong password",
"publish_app": "unable to publish app",
"rating_option": "cannot find rating option",
"save_qdn": "unable to save to QDN",
"send_failed": "failed to send",
"unable_encrypt_app": "unable to encrypt app. App not published'",
"unable_publish_app": "unable to publish app",
"unable_publish_image": "unable to publish image",
"unable_rate": "unable to rate",
"update_failed": "failed to update"
},
"generic": {
"avatar_size": "{{ size }} KB max. for GIFS",
"buy_order_request": "the Application <br/><italic>{{hostname}}</italic> <br/><span>is requesting {{count}} buy order</span>",
"buy_order_request_other": "the Application <br/><italic>{{hostname}}</italic> <br/><span>is requesting {{count}} buy orders</span>",
"devmode_local_node": "please use your local node for dev mode! Logout and use Local node.",
"edited": "edited",
"editing_message": "editing message",
"fee_qort": "fee: {{ message }} QORT",
"foreign_fee": "foreign fee: {{ message }}",
"mentioned": "mentioned",
"message_with_image": "this message already has an image",
"name_available": "{{ name }} is available",
"name_benefits": "benefits of a name",
"name_checking": "checking if name already exists",
"name_preview": "you need a name to use preview",
"name_publish": "you need a Qortal name to publish",
"name_rate": "you need a name to rate.",
"name_registration": "your balance is {{ balance }} QORT. A name registration requires a {{ fee }} QORT fee",
"name_unavailable": "{{ name }} is unavailable",
"no_description": "no description",
"no_minting_details": "cannot view minting details on the gateway",
"no_notifications": "no new notifications",
"no_pinned_changes": "you currently do not have any changes to your pinned apps",
"no_results": "no results",
"one_app_per_name": "note: Currently, only one App and Website is allowed per Name.",
"overwrite_qdn": "overwrite to QDN",
"password_confirm": "please confirm a password",
"password_enter": "please enter a password",
"payment_request": "the Application <br/><italic>{{hostname}}</italic> <br/><span>is requesting a payment</span>",
"people_reaction": "people who reacted with {{ reaction }}",
"publish_data": "publish data to Qortal: anything from apps to videos. Fully decentralized!",
"publishing": "publishing... Please wait.",
"qdn": "use QDN saving",
"rating": "rating for {{ service }} {{ name }}",
"register_name": "you need a registered Qortal name to save your pinned apps to QDN.",
"replied_to": "replied to {{ person }}",
"revert_default": "revert to default",
"revert_qdn": "revert to QDN",
"save_qdn": "save to QDN",
"secure_ownership": "secure ownership of data published by your name. You can even sell your name, along with your data to a third party.",
"select_file": "please select a file",
"select_image": "please select an image for a logo",
"select_zip": "select .zip file containing static content:",
"sending": "sending...",
"settings": "you are using the export/import way of saving settings.",
"space_for_admins": "sorry, this space is only for Admins.",
"unread_messages": "unread messages below",
"unsaved_changes": "you have unsaved changes to your pinned apps. Save them to QDN.",
"updating": "updating"
},
"question": {
"logout": "are you sure you would like to logout?",
"new_user": "are you a new user?",
"delete_chat_image": "would you like to delete your previous chat image?",
"perform_transaction": "would you like to perform a {{action}} transaction?",
"provide_thread": "please provide a thread title",
"publish_app": "would you like to publish this app?",
"publish_avatar": "would you like to publish an avatar?",
"publish_qdn": "would you like to publish your settings to QDN (encrypted)?",
"overwrite_changes": "the app was unable to download your existing QDN-saved pinned apps. Would you like to overwrite those changes?",
"rate_app": "would you like to rate this app a rating of {{ rate }}?. It will create a POLL tx.",
"register_name": "would you like to register this name?",
"reset_pinned": "don't like your current local changes? Would you like to reset to the default pinned apps?",
"reset_qdn": "don't like your current local changes? Would you like to reset to your saved QDN pinned apps?"
}, },
"status": { "status": {
"minting": "(minting)", "minting": "(minting)",
@ -74,12 +227,17 @@
}, },
"success": { "success": {
"order_submitted": "your buy order was submitted", "order_submitted": "your buy order was submitted",
"publish_qdn": "successfully published to QDN", "published": "successfully published. Please wait a couple minutes for the network to propogate the changes.",
"published_qdn": "successfully published to QDN",
"rated_app": "successfully rated. Please wait a couple minutes for the network to propogate the changes.",
"request_read": "I have read this request", "request_read": "I have read this request",
"transfer": "the transfer was succesful!" "transfer": "the transfer was succesful!"
} }
}, },
"minting_status": "minting status", "minting_status": "minting status",
"name": "name",
"name_app": "name/App",
"none": "none",
"page": { "page": {
"last": "last", "last": "last",
"first": "first", "first": "first",
@ -87,29 +245,21 @@
"previous": "previous" "previous": "previous"
}, },
"payment_notification": "payment notification", "payment_notification": "payment notification",
"port": "port",
"price": "price", "price": "price",
"q_mail": "q-mail", "q_apps": {
"question": { "about": "about this Q-App",
"new_user": "are you a new user?" "q_mail": "q-mail",
}, "q_manager": "q-manager",
"save_options": { "q_sandbox": "q-Sandbox"
"no_pinned_changes": "you currently do not have any changes to your pinned apps",
"overwrite_changes": "the app was unable to download your existing QDN-saved pinned apps. Would you like to overwrite those changes?",
"overwrite_qdn": "overwrite to QDN",
"publish_qdn": "would you like to publish your settings to QDN (encrypted)?",
"qdn": "use QDN saving",
"register_name": "you need a registered Qortal name to save your pinned apps to QDN.",
"reset_pinned": "don't like your current local changes? Would you like to reset to the default pinned apps?",
"reset_qdn": "don't like your current local changes? Would you like to reset to your saved QDN pinned apps?",
"revert_default": "revert to default",
"revert_qdn": "revert to QDN",
"save_qdn": "save to QDN",
"save": "save",
"settings": "you are using the export/import way of saving settings.",
"unsaved_changes": " you have unsaved changes to your pinned apps. Save them to QDN."
}, },
"server": "server",
"settings": "settings", "settings": "settings",
"sort": {
"by_member": "by member"
},
"supply": "supply", "supply": "supply",
"tags": "tags",
"theme": { "theme": {
"dark": "dark mode", "dark": "dark mode",
"light": "light mode" "light": "light mode"
@ -125,9 +275,13 @@
"title": "title", "title": "title",
"tutorial": "tutorial", "tutorial": "tutorial",
"user_lookup": "user lookup", "user_lookup": "user lookup",
"zip": "zip",
"wallet": { "wallet": {
"litecoin": "litecoin wallet",
"qortal": "qortal wallet",
"wallet": "wallet", "wallet": "wallet",
"wallet_other": "wallets" "wallet_other": "wallets"
}, },
"website": "website",
"welcome": "welcome" "welcome": "welcome"
} }

View File

@ -1,5 +1,6 @@
{ {
"action": { "action": {
"add_promotion": "add promotion",
"ban": "ban member from group", "ban": "ban member from group",
"cancel_ban": "cancel ban", "cancel_ban": "cancel ban",
"copy_private_key": "copy private key", "copy_private_key": "copy private key",
@ -16,9 +17,17 @@
"load_members": "load members with names", "load_members": "load members with names",
"make_admin": "make an admin", "make_admin": "make an admin",
"manage_members": "manage members", "manage_members": "manage members",
"promote_group": "promote your group to non-members",
"publish_announcement": "publish announcement",
"publish_avatar": "publish avatar",
"refetch_page": "refetch page", "refetch_page": "refetch page",
"remove_admin": "remove as admin", "remove_admin": "remove as admin",
"return_to_thread": "return to threads" "remove_minting_account": "remove minting account",
"return_to_thread": "return to threads",
"scroll_bottom": "scroll to bottom",
"scroll_unread_messages": "scroll to Unread Messages",
"select_group": "select a group",
"visit_q_mintership": "visit Q-Mintership"
}, },
"advanced_options": "advanced options", "advanced_options": "advanced options",
"approval_threshold": "group Approval Threshold (number / percentage of Admins that must approve a transaction)", "approval_threshold": "group Approval Threshold (number / percentage of Admins that must approve a transaction)",
@ -28,14 +37,21 @@
"maximum": "maximum Block delay for Group Transaction Approvals" "maximum": "maximum Block delay for Group Transaction Approvals"
}, },
"group": { "group": {
"avatar": "group avatar",
"closed": "closed (private) - users need permission to join", "closed": "closed (private) - users need permission to join",
"description": "description of group", "description": "description of group",
"id": "group id", "id": "group id",
"invites": "group invites", "invites": "group invites",
"group": "group",
"group_other": "groups",
"management": "group management", "management": "group management",
"member_number": "number of members", "member_number": "number of members",
"messaging": "messaging",
"name": "group name", "name": "group name",
"open": "open (public)", "open": "open (public)",
"private": "private group",
"promotions": "group promotions",
"public": "public group",
"type": "group type" "type": "group type"
}, },
"invitation_expiry": "invitation Expiry Time", "invitation_expiry": "invitation Expiry Time",
@ -46,16 +62,40 @@
"latest_mails": "latest Q-Mails", "latest_mails": "latest Q-Mails",
"message": { "message": {
"generic": { "generic": {
"avatar_publish_fee": "publishing an Avatar requires {{ fee }}",
"avatar_registered_name": "a registered name is required to set an avatar",
"admin_only": "only groups where you are an admin will be shown",
"already_in_group": "you are already in this group!", "already_in_group": "you are already in this group!",
"closed_group": "this is a closed/private group, so you will need to wait until an admin accepts your request", "closed_group": "this is a closed/private group, so you will need to wait until an admin accepts your request",
"descrypt_wallet": "decrypting wallet...", "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...", "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_announcement": "group Announcements",
"group_invited_you": "{{group}} has invited you", "group_invited_you": "{{group}} has invited you",
"group_key_created": "first group key created.",
"group_member_list_changed": "the group member list has changed. Please re-encrypt the secret key.",
"group_no_secret_key": "there is no group secret key. Be the first admin to publish one!",
"group_secret_key_no_owner": "the latest group secret key was published by a non-owner. As the owner of the group please re-encrypt the key as a safeguard.",
"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.", "loading_members": "loading member list with names... please wait.",
"max_chars": "max 200 characters. Publish Fee",
"manage_minting": "manage your minting",
"minter_group": "you are currently not part of the MINTER group",
"mintership_app": "visit the Q-Mintership app to apply to be a minter",
"minting_account": "minting account:",
"minting_keys_per_node": "only 2 minting keys are allowed per node. Please remove one if you would like to mint with this account.",
"minting_keys_per_node_different": "only 2 minting keys are allowed per node. Please remove one if you would like to add a different account.",
"next_level": "blocks remaining until next level:",
"node_minting": "This node is minting:",
"node_minting_account": "node's minting accounts",
"node_minting_key": "you currently have a minting key for this account attached to this node",
"no_announcement": "no announcements",
"no_display": "nothing to display", "no_display": "nothing to display",
"no_selection": "no group selected", "no_selection": "no group selected",
"not_part_group": "you are not part of the encrypted group of members. Wait until an admin re-encrypts the keys.", "not_part_group": "you are not part of the encrypted group of members. Wait until an admin re-encrypts the keys.",
"only_encrypted": "only unencrypted messages will be displayed.", "only_encrypted": "only unencrypted messages will be displayed.",
"only_private_groups": "only private groups will be shown",
"private_key_copied": "private key copied", "private_key_copied": "private key copied",
"provide_message": "please provide a first message to the thread", "provide_message": "please provide a first message to the thread",
"secure_place": "keep your private key in a secure place. Do not share!", "secure_place": "keep your private key in a secure place. Do not share!",
@ -66,10 +106,15 @@
"descrypt_wallet": "error decrypting wallet {{ :errorMessage }}", "descrypt_wallet": "error decrypting wallet {{ :errorMessage }}",
"description_required": "please provide a description", "description_required": "please provide a description",
"group_info": "cannot access group information", "group_info": "cannot access group information",
"group_promotion": "error publishing the promotion. Please try again",
"group_secret_key": "cannot get group secret key", "group_secret_key": "cannot get group secret key",
"name_required": "please provide a name", "name_required": "please provide a name",
"notify_admins": "try notifying an admin from the list of admins below:", "notify_admins": "try notifying an admin from the list of admins below:",
"thread_id": "unable to locate thread Id" "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": { "success": {
"group_ban": "successfully banned member from group. It may take a couple of minutes for the changes to propagate", "group_ban": "successfully banned member from group. It may take a couple of minutes for the changes to propagate",
@ -87,19 +132,27 @@
"group_leave_name": "left group {{group_name}}: awaiting confirmation", "group_leave_name": "left group {{group_name}}: awaiting confirmation",
"group_leave_label": "left group {{name}}: success!", "group_leave_label": "left group {{name}}: success!",
"group_member_admin": "successfully made member an admin. It may take a couple of minutes for the changes to propagate", "group_member_admin": "successfully made member an admin. It may take a couple of minutes for the changes to propagate",
"group_promotion": "successfully published promotion. It may take a couple of minutes for the promotion to appear",
"group_remove_member": "successfully removed member as an admin. It may take a couple of minutes for the changes to propagate", "group_remove_member": "successfully removed member as an admin. It may take a couple of minutes for the changes to propagate",
"invitation_cancellation": "successfully canceled invitation. It may take a couple of minutes for the changes to propagate", "invitation_cancellation": "successfully canceled invitation. It may take a couple of minutes for the changes to propagate",
"invitation_request": "accepted join request: awaiting confirmation", "invitation_request": "accepted join request: awaiting confirmation",
"loading_threads": "loading threads... please wait.", "loading_threads": "loading threads... please wait.",
"post_creation": "successfully created post. It may take some time for the publish to propagate", "post_creation": "successfully created post. It may take some time for the publish to propagate",
"published_secret_key": "published secret key for group {{ group_id }}: awaiting confirmation",
"published_secret_key_label": "published secret key for group {{ group_id }}: success!",
"registered_name": "successfully registered. It may take a couple of minutes for the changes to propagate",
"registered_name_label": "registered name: awaiting confirmation. This may take a couple minutes.",
"registered_name_success": "registered name: success!",
"rewardshare_add": "add rewardshare: awaiting confirmation",
"rewardshare_add_label": "add rewardshare: success!",
"rewardshare_creation": "confirming creation of rewardshare on chain. Please be patient, this could take up to 90 seconds.",
"rewardshare_confirmed": "rewardshare confirmed. Please click Next.",
"rewardshare_remove": "remove rewardshare: awaiting confirmation",
"rewardshare_remove_label": "remove rewardshare: success!",
"thread_creation": "successfully created thread. It may take some time for the publish to propagate", "thread_creation": "successfully created thread. It may take some time for the publish to propagate",
"unbanned_user": "successfully unbanned user. It may take a couple of minutes for the changes to propagate", "unbanned_user": "successfully unbanned user. It may take a couple of minutes for the changes to propagate",
"user_joined": "user successfully joined!" "user_joined": "user successfully joined!"
} }
}, },
"question": {
"perform_transaction": "would you like to perform a {{action}} transaction?",
"provide_thread": "please provide a thread title"
},
"thread_posts": "new thread posts" "thread_posts": "new thread posts"
} }

View File

@ -2,22 +2,62 @@
"account": { "account": {
"your": "tu cuenta", "your": "tu cuenta",
"account_many": "cuentas", "account_many": "cuentas",
"account_one": "cuenta" "account_one": "cuenta",
"selected": "cuenta seleccionada"
},
"action": {
"add": {
"account": "añadir cuenta",
"seed_phrase": "añadir frase semilla"
},
"authenticate": "autenticar",
"create_account": "crear cuenta",
"create_qortal_account": "crea tu cuenta Qortal haciendo clic en <next>SIGUIENTE</next> abajo.",
"choose_password": "elige nueva contraseña",
"download_account": "descargar cuenta",
"export_seedphrase": "exportar frase semilla",
"publish_admin_secret_key": "publicar clave secreta de administrador",
"publish_group_secret_key": "publicar clave secreta de grupo",
"reencrypt_key": "reencriptar clave",
"return_to_list": "volver a la lista",
"setup_qortal_account": "configurar tu cuenta Qortal"
}, },
"advanced_users": "para usuarios avanzados", "advanced_users": "para usuarios avanzados",
"apikey": { "apikey": {
"alternative": "alternativa: Seleccionar archivo", "alternative": "alternativa: seleccionar archivo",
"change": "cambiar clave API", "change": "cambiar API key",
"enter": "ingresar clave API", "enter": "ingresar API key",
"import": "importar clave API", "import": "importar API key",
"key": "clave API", "key": "clave API",
"select_valid": "selecciona una clave API válida" "select_valid": "selecciona una API key válida"
}, },
"authenticate": "autenticar",
"build_version": "versión de compilación", "build_version": "versión de compilación",
"create_account": "crear cuenta", "message": {
"download_account": "descargar cuenta", "error": {
"keep_secure": "mantén tu archivo de cuenta seguro", "account_creation": "no se pudo crear la cuenta.",
"field_not_found_json": "{{ field }} no encontrado en el JSON",
"incorrect_password": "contraseña incorrecta",
"invalid_secret_key": "clave secreta no válida",
"unable_reencrypt_secret_key": "no se pudo reencriptar la clave secreta"
},
"generic": {
"congrats_setup": "¡felicidades, estás listo!",
"no_account": "no hay cuentas guardadas",
"no_minimum_length": "no hay un requisito de longitud mínima",
"no_secret_key_published": "aún no se ha publicado una clave secreta",
"fetching_admin_secret_key": "obteniendo clave secreta de administrador",
"fetching_group_secret_key": "publicando clave secreta de grupo",
"last_encryption_date": "última fecha de encriptación: {{ date }} por {{ name }}",
"keep_secure": "mantén tu archivo de cuenta seguro",
"publishing_key": "recordatorio: después de publicar la clave, tardará un par de minutos en aparecer. Por favor espera.",
"seedphrase_notice": "una <seed>FRASE SEMILLA</seed> se ha generado aleatoriamente en segundo plano.",
"type_seed": "escribe o pega tu frase semilla",
"your_accounts": "tus cuentas guardadas"
},
"success": {
"reencrypted_secret_key": "clave secreta reencriptada con éxito. Puede tardar unos minutos en reflejarse. Actualiza el grupo en 5 minutos."
}
},
"node": { "node": {
"choose": "elegir nodo personalizado", "choose": "elegir nodo personalizado",
"custom_many": "nodos personalizados", "custom_many": "nodos personalizados",
@ -26,17 +66,31 @@
"using": "usando nodo", "using": "usando nodo",
"using_public": "usando nodo público" "using_public": "usando nodo público"
}, },
"note": "nota",
"password": "contraseña", "password": "contraseña",
"password_confirmation": "confirmar contraseña", "password_confirmation": "confirmar contraseña",
"return_to_list": "volver a la lista", "seed": "frase semilla",
"seed_your": "tu frase semilla",
"tips": {
"additional_wallet": "usa esta opción para conectar billeteras Qortal adicionales que ya hayas creado, para poder iniciar sesión con ellas más tarde. Necesitarás acceso a tu archivo de respaldo JSON.",
"digital_id": "tu billetera es como tu identificación digital en Qortal, y es la forma en que iniciarás sesión en la interfaz de usuario de Qortal. Contiene tu dirección pública y el nombre de Qortal que eventualmente elegirás. Cada transacción está vinculada a tu ID, y aquí es donde gestionas todo tu QORT y otras criptomonedas intercambiables en Qortal.",
"existing_account": "¿ya tienes una cuenta Qortal? Ingresa aquí tu frase de respaldo secreta para acceder. Esta frase es una de las formas de recuperar tu cuenta.",
"key_encrypt_admin": "esta clave es para cifrar contenido relacionado con ADMIN. Solo los administradores podrán ver el contenido cifrado con ella.",
"key_encrypt_group": "esta clave es para cifrar contenido relacionado con GRUPO. Es la única utilizada en esta interfaz. Todos los miembros del grupo podrán ver el contenido cifrado con esta clave.",
"new_account": "crear una cuenta significa crear una nueva billetera e identificación digital para comenzar a usar Qortal. Una vez que hayas creado tu cuenta, puedes empezar a obtener QORT, comprar un nombre y avatar, publicar videos y blogs, y mucho más.",
"new_users": "¡los nuevos usuarios comienzan aquí!",
"safe_place": "guarda tu cuenta en un lugar que recuerdes.",
"view_seedphrase": "si deseas VER LA FRASE SEMILLA, haz clic en la palabra 'FRASE SEMILLA' en este texto. Las frases semilla se usan para generar la clave privada de tu cuenta Qortal. Por seguridad, no se muestran por defecto.",
"wallet_secure": "mantén segura tu billetera."
},
"wallet": { "wallet": {
"password_confirmation": "confirmar contraseña del monedero", "password_confirmation": "confirmar contraseña de la billetera",
"password": "contraseña del monedero", "password": "contraseña de la billetera",
"keep_password": "mantener la contraseña actual", "keep_password": "mantener la contraseña actual",
"new_password": "nueva contraseña", "new_password": "nueva contraseña",
"error": { "error": {
"missing_new_password": "por favor ingresa una nueva contraseña", "missing_new_password": "por favor, ingresa una nueva contraseña",
"missing_password": "por favor ingresa tu contraseña" "missing_password": "por favor, ingresa tu contraseña"
} }
}, },
"welcome": "bienvenido a" "welcome": "bienvenido a"

View File

@ -1,38 +1,92 @@
{ {
"account": { "account": {
"your": "ton compte", "your": "votre compte",
"account_many": "comptes", "account_many": "comptes",
"account_one": "compte" "account_one": "compte",
"selected": "compte sélectionné"
},
"action": {
"add": {
"account": "ajouter un compte",
"seed_phrase": "ajouter une phrase secrète"
},
"authenticate": "authentifier",
"create_account": "créer un compte",
"create_qortal_account": "créez votre compte Qortal en cliquant sur <next>SUIVANT</next> ci-dessous.",
"choose_password": "choisissez un nouveau mot de passe",
"download_account": "télécharger le compte",
"export_seedphrase": "exporter la phrase secrète",
"publish_admin_secret_key": "publier la clé secrète admin",
"publish_group_secret_key": "publier la clé secrète du groupe",
"reencrypt_key": "re-chiffrer la clé",
"return_to_list": "retour à la liste",
"setup_qortal_account": "configurer votre compte Qortal"
}, },
"advanced_users": "pour les utilisateurs avancés", "advanced_users": "pour les utilisateurs avancés",
"apikey": { "apikey": {
"alternative": "alternative : Sélectionner un fichier", "alternative": "alternative : sélection de fichier",
"change": "changer la clé API", "change": "changer la clé API",
"enter": "entrer la clé API", "enter": "entrer la clé API",
"import": "importer la clé API", "import": "importer la clé API",
"key": "clé API", "key": "clé API",
"select_valid": "sélectionnez une clé API valide" "select_valid": "sélectionner une clé API valide"
}, },
"authenticate": "authentifier",
"build_version": "version de build", "build_version": "version de build",
"create_account": "créer un compte", "message": {
"download_account": "télécharger le compte", "error": {
"keep_secure": "Gardez votre fichier de compte en sécurité", "account_creation": "impossible de créer le compte.",
"field_not_found_json": "{{ field }} introuvable dans le JSON",
"incorrect_password": "mot de passe incorrect",
"invalid_secret_key": "clé secrète invalide",
"unable_reencrypt_secret_key": "impossible de re-chiffrer la clé secrète"
},
"generic": {
"congrats_setup": "félicitations, tout est prêt !",
"no_account": "aucun compte enregistré",
"no_minimum_length": "aucune exigence de longueur minimale",
"no_secret_key_published": "aucune clé secrète publiée pour le moment",
"fetching_admin_secret_key": "récupération de la clé secrète admin",
"fetching_group_secret_key": "publication de la clé secrète du groupe",
"last_encryption_date": "dernière date de chiffrement : {{ date }} par {{ name }}",
"keep_secure": "gardez votre fichier de compte en sécurité",
"publishing_key": "rappel : après publication, la clé peut mettre quelques minutes à apparaître. Veuillez patienter.",
"seedphrase_notice": "une <seed>PHRASE SECRÈTE</seed> a été générée aléatoirement en arrière-plan.",
"type_seed": "saisissez ou collez votre phrase secrète",
"your_accounts": "vos comptes enregistrés"
},
"success": {
"reencrypted_secret_key": "clé secrète re-chiffrée avec succès. Les modifications peuvent prendre quelques minutes pour se propager. Rafraîchissez le groupe dans 5 minutes."
}
},
"node": { "node": {
"choose": "choisir un nœud personnalisé", "choose": "choisir un nœud personnalisé",
"custom_many": "nœuds personnalisés", "custom_many": "nœuds personnalisés",
"use_custom": "utiliser un nœud personnalisé", "use_custom": "utiliser un nœud personnalisé",
"use_local": "utiliser un nœud local", "use_local": "utiliser un nœud local",
"using": "utilise le nœud", "using": "utilisation du nœud",
"using_public": "utilise un nœud public" "using_public": "utilisation dun nœud public"
}, },
"note": "note",
"password": "mot de passe", "password": "mot de passe",
"password_confirmation": "confirmer le mot de passe", "password_confirmation": "confirmer le mot de passe",
"return_to_list": "retour à la liste", "seed": "phrase secrète",
"seed_your": "votre phrase secrète",
"tips": {
"additional_wallet": "utilisez cette option pour connecter d'autres portefeuilles Qortal que vous avez déjà créés, afin de pouvoir vous y connecter ultérieurement. Vous aurez besoin de votre fichier de sauvegarde JSON.",
"digital_id": "votre portefeuille est comme votre identifiant numérique sur Qortal, et cest ainsi que vous vous connecterez à linterface utilisateur Qortal. Il contient votre adresse publique et le nom Qortal que vous choisirez. Chaque transaction est liée à votre ID, et cest là que vous gérez votre QORT et d'autres cryptomonnaies échangeables sur Qortal.",
"existing_account": "vous avez déjà un compte Qortal ? Entrez ici votre phrase secrète de sauvegarde pour y accéder. Cest un des moyens de récupérer votre compte.",
"key_encrypt_admin": "cette clé est utilisée pour chiffrer le contenu ADMIN. Seuls les administrateurs peuvent voir ce contenu.",
"key_encrypt_group": "cette clé est utilisée pour chiffrer le contenu du GROUPE. Cest la seule utilisée dans cette interface pour le moment. Tous les membres du groupe pourront voir le contenu chiffré avec cette clé.",
"new_account": "créer un compte signifie créer un nouveau portefeuille et identifiant numérique pour commencer à utiliser Qortal. Une fois votre compte créé, vous pourrez obtenir du QORT, acheter un nom et un avatar, publier des vidéos et blogs, et bien plus.",
"new_users": "nouveaux utilisateurs, commencez ici !",
"safe_place": "sauvegardez votre compte dans un endroit sûr et mémorable !",
"view_seedphrase": "si vous souhaitez VOIR LA PHRASE SECRÈTE, cliquez sur le mot 'PHRASE SECRÈTE' dans ce texte. Les phrases secrètes servent à générer la clé privée de votre compte Qortal. Par sécurité, elles ne sont PAS affichées par défaut.",
"wallet_secure": "gardez votre fichier de portefeuille sécurisé."
},
"wallet": { "wallet": {
"password_confirmation": "confirmer le mot de passe du portefeuille", "password_confirmation": "confirmer le mot de passe du portefeuille",
"password": "mot de passe du portefeuille", "password": "mot de passe du portefeuille",
"keep_password": "garder le mot de passe actuel", "keep_password": "conserver le mot de passe actuel",
"new_password": "nouveau mot de passe", "new_password": "nouveau mot de passe",
"error": { "error": {
"missing_new_password": "veuillez entrer un nouveau mot de passe", "missing_new_password": "veuillez entrer un nouveau mot de passe",

View File

@ -2,47 +2,96 @@
"account": { "account": {
"your": "il tuo account", "your": "il tuo account",
"account_many": "account", "account_many": "account",
"account_one": "account" "account_one": "account",
"selected": "account selezionato"
}, },
"advanced_users": "per utenti avanzati", "action": {
"add": {
"account": "aggiungi account",
"seed_phrase": "aggiungi frase segreta"
},
"authenticate": "autenticazione",
"create_account": "crea account",
"create_qortal_account": "crea il tuo account Qortal cliccando su <next>AVANTI</next> qui sotto.",
"choose_password": "scegli una nuova password",
"download_account": "scarica account",
"export_seedphrase": "esporta frase segreta",
"publish_admin_secret_key": "pubblica chiave segreta admin",
"publish_group_secret_key": "pubblica chiave segreta gruppo",
"reencrypt_key": "ricripta chiave",
"return_to_list": "torna alla lista",
"setup_qortal_account": "configura il tuo account Qortal"
},
"advanced_users": "per utenti esperti",
"apikey": { "apikey": {
"alternative": "alternativa: selezione file", "alternative": "alternativa: seleziona file",
"change": "cambia APIkey", "change": "cambia chiave API",
"enter": "inserisci APIkey", "enter": "inserisci chiave API",
"import": "importa APIkey", "import": "importa chiave API",
"key": "chiave API", "key": "chiave API",
"select_valid": "seleziona una APIkey valida" "select_valid": "seleziona una chiave API valida"
}, },
"authenticate": "autentica",
"build_version": "versione build", "build_version": "versione build",
"create_account": "crea account", "message": {
"download_account": "scarica account", "error": {
"keep_secure": "mantieni sicuro il file del tuo account", "account_creation": "impossibile creare laccount.",
"field_not_found_json": "{{ field }} non trovato nel JSON",
"incorrect_password": "password errata",
"invalid_secret_key": "chiave segreta non valida",
"unable_reencrypt_secret_key": "impossibile ricriptare la chiave segreta"
},
"generic": {
"congrats_setup": "congratulazioni, sei pronto!",
"no_account": "nessun account salvato",
"no_minimum_length": "nessun requisito di lunghezza minima",
"no_secret_key_published": "nessuna chiave segreta pubblicata",
"fetching_admin_secret_key": "recupero chiave segreta admin",
"fetching_group_secret_key": "pubblicazione chiave segreta gruppo",
"last_encryption_date": "ultima data di cifratura: {{ date }} da {{ name }}",
"keep_secure": "mantieni sicuro il tuo file account",
"publishing_key": "promemoria: dopo la pubblicazione della chiave, ci vorranno alcuni minuti per vederla. Attendere cortesemente.",
"seedphrase_notice": "una <seed>FRASE SEGRETA</seed> è stata generata automaticamente in background.",
"type_seed": "digita o incolla la tua frase segreta",
"your_accounts": "i tuoi account salvati"
},
"success": {
"reencrypted_secret_key": "chiave segreta ricriptata con successo. Potrebbero volerci alcuni minuti per vedere le modifiche. Aggiorna il gruppo tra 5 minuti."
}
},
"node": { "node": {
"choose": "scegli nodo personalizzato", "choose": "scegli nodo personalizzato",
"custom_many": "nodi personalizzati", "custom_many": "nodi personalizzati",
"use_custom": "usa nodo personalizzato", "use_custom": "usa nodo personalizzato",
"use_local": "usa nodo locale", "use_local": "usa nodo locale",
"using": "utilizzo nodo", "using": "utilizzo nodo",
"using_public": "utilizzo nodo pubblico" "using_public": "utilizzo di nodo pubblico"
}, },
"note": "nota",
"password": "password", "password": "password",
"password_confirmation": "conferma password", "password_confirmation": "conferma password",
"return_to_list": "torna alla lista", "seed": "frase segreta",
"seed_your": "la tua frase segreta",
"tips": { "tips": {
"digital_id": "il tuo wallet è come la tua identità digitale su Qortal ed è il modo in cui accederai all'interfaccia utente di Qortal. Contiene il tuo indirizzo pubblico e il nome Qortal che sceglierai. Ogni transazione che esegui è collegata alla tua identità ed è qui che gestisci tutti i tuoi QORT e altre criptovalute scambiabili su Qortal.", "additional_wallet": "usa questa opzione per connettere altri portafogli Qortal che hai già creato, per accedervi successivamente. Avrai bisogno del file JSON di backup.",
"new_account": "creare un account significa creare un nuovo wallet e un'identità digitale per iniziare a usare Qortal. Una volta creato l'account, potrai iniziare a ottenere QORT, acquistare un nome e un avatar, pubblicare video e blog, e molto altro.", "digital_id": "il tuo portafoglio è come la tua ID digitale su Qortal, ed è il modo con cui accedi allinterfaccia utente. Contiene il tuo indirizzo pubblico e il nome Qortal che sceglierai. Ogni transazione è legata alla tua ID, e qui gestisci QORT e altre criptovalute scambiabili su Qortal.",
"new_users": "i nuovi utenti iniziano qui!" "existing_account": "hai già un account Qortal? Inserisci qui la tua frase di backup segreta per accedervi. Questa frase è uno dei modi per recuperare l'account.",
"key_encrypt_admin": "questa chiave serve a criptare contenuti legati agli ADMIN. Solo gli admin potranno vederli.",
"key_encrypt_group": "questa chiave serve a criptare contenuti di GRUPPO. È lunica usata in questa interfaccia. Tutti i membri del gruppo potranno vederli.",
"new_account": "creare un account significa creare un nuovo portafoglio e una nuova ID digitale per iniziare a usare Qortal. Una volta creato laccount, potrai ottenere QORT, acquistare un nome e avatar, pubblicare video e blog, e molto altro.",
"new_users": "nuovi utenti, iniziate qui!",
"safe_place": "salva il tuo account in un posto sicuro che ricorderai!",
"view_seedphrase": "se vuoi VEDERE LA FRASE SEGRETA, clicca sulla parola 'FRASE SEGRETA' in questo testo. Le frasi segrete generano la chiave privata del tuo account. Per sicurezza, non vengono mostrate di default.",
"wallet_secure": "mantieni sicuro il tuo file portafoglio."
}, },
"wallet": { "wallet": {
"password_confirmation": "conferma password del wallet", "password_confirmation": "conferma password portafoglio",
"password": "password del wallet", "password": "password del portafoglio",
"keep_password": "mantieni password corrente", "keep_password": "mantieni password attuale",
"new_password": "nuova password", "new_password": "nuova password",
"error": { "error": {
"missing_new_password": "per favore inserisci una nuova password", "missing_new_password": "inserisci una nuova password",
"missing_password": "per favore inserisci la tua password" "missing_password": "inserisci la tua password"
} }
}, },
"welcome": "benvenuto in" "welcome": "benvenuto su"
} }

View File

@ -1,41 +1,98 @@
{ {
"action": { "action": {
"add": "aggiungi", "add": "aggiungi",
"add_custom_framework": "aggiungi custom framework",
"add_reaction": "adaggiungid reazione",
"accept": "accetta", "accept": "accetta",
"access": "accedi",
"backup_account": "esegui backup account", "backup_account": "esegui backup account",
"backup_wallet": "esegui backup portafoglio", "backup_wallet": "esegui backup wallet",
"cancel": "annulla", "cancel": "cancella",
"cancel_invitation": "annulla invito", "cancel_invitation": "cancella invito",
"change": "cambia", "change": "cambia",
"change_avatar": "cambia avatar",
"change_file": "cambia file",
"change_language": "cambia lingua", "change_language": "cambia lingua",
"choose": "scegli", "choose": "scegli",
"choose_file": "scegli file",
"choose_image": "scegli immagine",
"close": "chiudi", "close": "chiudi",
"close_chat": "chiudi Chat Diretta",
"continue": "continua", "continue": "continua",
"continue_logout": "continua con il logout", "continue_logout": "continua il logout",
"create_thread": "crea discussione", "copy_link": "copia link",
"decline": "rifiuta", "create_apps": "crea apps",
"decrypt": "decifra", "create_file": "crea file",
"create_thread": "crea thread",
"choose_logo": "scegli a logo",
"choose_name": "scegli a name",
"decline": "declina",
"decrypt": "decripta",
"disable_enter": "disabilita invio",
"download": "download",
"edit": "modifica", "edit": "modifica",
"enter_name": "inserisci un nome",
"export": "esporta", "export": "esporta",
"get_qort": "ottieni QORT in Q-Trade",
"hide": "nascondi",
"import": "importa", "import": "importa",
"invite": "invita", "invite": "invita",
"join": "unisciti", "join": "unisciti",
"logout": "esci", "leave_comment": "lascia un commento",
"load_announcements": "load older announcements",
"login": "login",
"logout": "logout",
"new": { "new": {
"post": "nuovo post", "post": "nuovo post",
"thread": "nuova discussione" "thread": "nuova discussione"
}, },
"notify": "notifica", "notify": "notify",
"post": "pubblica", "open": "open",
"post_message": "invia messaggio" "pin": "pin",
"pin_app": "pin app",
"pin_from_dashboard": "pin from dashboard",
"post": "post",
"post_message": "post message",
"publish": "publish",
"publish_app": "publish your app",
"publish_comment": "publish comment",
"register_name": "register name",
"remove": "remove",
"remove_reaction": "remove reaction",
"return_apps_dashboard": "return to Apps Dashboard",
"save": "save",
"search": "search",
"search_apps": "search for apps",
"select_app_type": "select App Type",
"select_category": "select Category",
"select_name_app": "select Name/App",
"set_avatar": "set avatar",
"start_minting": "start minting",
"start_typing": "start typing here...",
"transfer_qort": "Transfer QORT",
"unpin": "unpin",
"unpin_app": "unpin app",
"unpin_from_dashboard": "unpin from dashboard",
"update": "update",
"update_app": "update your app"
}, },
"admin": "amministratore", "admin": "admin",
"all": "all",
"api": "API",
"app": "app",
"app_name": "app name",
"app_service_type": "app service type",
"apps_dashboard": "apps Dashboard",
"apps_official": "official Apps",
"category": "category",
"category_other": "categories",
"core": { "core": {
"block_height": "altezza blocco", "block_height": "altezza blocco",
"information": "informazioni core", "information": "informazioni core",
"peers": "peer connessi", "peers": "peer connessi",
"version": "versione core" "version": "versione core"
}, },
"domain": "domain",
"ui": { "ui": {
"version": "versione UI" "version": "versione UI"
}, },
@ -44,27 +101,123 @@
"one": "uno" "one": "uno"
}, },
"description": "descrizione", "description": "descrizione",
"downloading_qdn": "scaricamento da QDN", "devmode_apps": "dev Mode Apps",
"directory": "directory",
"downloading_qdn": "download da QDN",
"fee": { "fee": {
"payment": "commissione di pagamento", "payment": "commissione di pagamento",
"publish": "commissione di pubblicazione" "publish": "commissione di pubblicazione"
}, },
"for": "per",
"general_settings": "impostazioni generali", "general_settings": "impostazioni generali",
"last_height": "ultima altezza", "identifier": "identificatore",
"last_height": "altezza ultima",
"level": "livello",
"library": "libreria",
"list": { "list": {
"invite": "lista inviti", "invite": "lista inviti",
"join_request": "lista richieste di adesione", "join_request": "lista richieste di adesione",
"member": "lista membri" "member": "lista membri"
}, },
"loading": "caricamento...", "loading": {
"loading_posts": "caricamento post... attendere.", "announcements": "annunci",
"message_us": "contattaci su Telegram o Discord se hai bisogno di 4 QORT per iniziare a chattare senza limitazioni", "generic": "caricamento...",
"chat": "caricamento chat... attendere, per favore.",
"comments": "caricamento commenti... attendere, per favore.",
"posts": "caricamento post... attendere, per favore."
},
"message_us": "please message us on Telegram or Discord if you need 4 QORT to start chatting without any limitations",
"message": { "message": {
"error": { "error": {
"generic": "si è verificato un errore", "address_not_found": "your address was not found",
"incorrect_password": "password errata", "app_need_name": "your app needs a name",
"missing_field": "manca: {{ field }}", "file_too_large": "file {{ filename }} is too large. Max size allowed is {{ size }} MB.",
"save_qdn": "impossibile salvare su QDN" "generic": "an error occurred",
"invalid_signature": "invalid signature",
"invalid_zip": "invalid zip",
"message_loading": "error loading message.",
"message_size": "your message size is of {{ size }} bytes out of a maximum of {{ maximum }}",
"minting_account_add": "unable to add minting account",
"minting_account_remove": "unable to remove minting account",
"missing_fields": "missing: {{ fields }}",
"navigation_timeout": "navigation timeout",
"network_generic": "network error",
"password_not_matching": "password fields do not match!",
"password_wrong": "unable to authenticate. Wrong password",
"publish_app": "unable to publish app",
"rating_option": "cannot find rating option",
"save_qdn": "unable to save to QDN",
"send_failed": "failed to send",
"unable_encrypt_app": "unable to encrypt app. App not published'",
"unable_publish_app": "unable to publish app",
"unable_publish_image": "unable to publish image",
"unable_rate": "unable to rate",
"update_failed": "failed to update"
},
"generic": {
"avatar_size": "{{ size }} KB max. for GIFS",
"buy_order_request": "the Application <br/><italic>{{hostname}}</italic> <br/><span>is requesting {{count}} buy order</span>",
"buy_order_request_other": "the Application <br/><italic>{{hostname}}</italic> <br/><span>is requesting {{count}} buy orders</span>",
"devmode_local_node": "please use your local node for dev mode! Logout and use Local node.",
"edited": "edited",
"editing_message": "editing message",
"fee_qort": "fee: {{ message }} QORT",
"foreign_fee": "foreign fee: {{ message }}",
"mentioned": "mentioned",
"message_with_image": "this message already has an image",
"name_available": "{{ name }} is available",
"name_benefits": "benefits of a name",
"name_checking": "checking if name already exists",
"name_preview": "you need a name to use preview",
"name_publish": "you need a Qortal name to publish",
"name_rate": "you need a name to rate.",
"name_registration": "your balance is {{ balance }} QORT. A name registration requires a {{ fee }} QORT fee",
"name_unavailable": "{{ name }} is unavailable",
"no_description": "no description",
"no_minting_details": "cannot view minting details on the gateway",
"no_notifications": "no new notifications",
"no_pinned_changes": "you currently do not have any changes to your pinned apps",
"no_results": "no results",
"one_app_per_name": "note: Currently, only one App and Website is allowed per Name.",
"overwrite_qdn": "overwrite to QDN",
"password_confirm": "please confirm a password",
"password_enter": "please enter a password",
"payment_request": "the Application <br/><italic>{{hostname}}</italic> <br/><span>is requesting a payment</span>",
"people_reaction": "people who reacted with {{ reaction }}",
"publish_data": "publish data to Qortal: anything from apps to videos. Fully decentralized!",
"publishing": "publishing... Please wait.",
"qdn": "use QDN saving",
"rating": "rating for {{ service }} {{ name }}",
"register_name": "you need a registered Qortal name to save your pinned apps to QDN.",
"replied_to": "replied to {{ person }}",
"revert_default": "revert to default",
"revert_qdn": "revert to QDN",
"save_qdn": "save to QDN",
"secure_ownership": "secure ownership of data published by your name. You can even sell your name, along with your data to a third party.",
"select_file": "please select a file",
"select_image": "please select an image for a logo",
"select_zip": "select .zip file containing static content:",
"sending": "sending...",
"settings": "you are using the export/import way of saving settings.",
"space_for_admins": "sorry, this space is only for Admins.",
"unread_messages": "unread messages below",
"unsaved_changes": "you have unsaved changes to your pinned apps. Save them to QDN.",
"updating": "updating"
},
"question": {
"logout": "are you sure you would like to logout?",
"new_user": "are you a new user?",
"delete_chat_image": "would you like to delete your previous chat image?",
"perform_transaction": "would you like to perform a {{action}} transaction?",
"provide_thread": "please provide a thread title",
"publish_app": "would you like to publish this app?",
"publish_avatar": "would you like to publish an avatar?",
"publish_qdn": "would you like to publish your settings to QDN (encrypted)?",
"overwrite_changes": "the app was unable to download your existing QDN-saved pinned apps. Would you like to overwrite those changes?",
"rate_app": "would you like to rate this app a rating of {{ rate }}?. It will create a POLL tx.",
"register_name": "would you like to register this name?",
"reset_pinned": "don't like your current local changes? Would you like to reset to the default pinned apps?",
"reset_qdn": "don't like your current local changes? Would you like to reset to your saved QDN pinned apps?"
}, },
"status": { "status": {
"minting": "(in conio)", "minting": "(in conio)",
@ -73,13 +226,18 @@
"synchronizing": "sincronizzazione" "synchronizing": "sincronizzazione"
}, },
"success": { "success": {
"order_submitted": "il tuo ordine di acquisto è stato inviato", "order_submitted": "your buy order was submitted",
"publish_qdn": "pubblicato con successo su QDN", "published": "successfully published. Please wait a couple minutes for the network to propogate the changes.",
"request_read": "ho letto questa richiesta", "published_qdn": "successfully published to QDN",
"transfer": "il trasferimento è riuscito!" "rated_app": "successfully rated. Please wait a couple minutes for the network to propogate the changes.",
"request_read": "I have read this request",
"transfer": "the transfer was succesful!"
} }
}, },
"minting_status": "stato conio", "minting_status": "stato del conio",
"name": "nome",
"name_app": "nome/App",
"none": "nessuno",
"page": { "page": {
"last": "ultima", "last": "ultima",
"first": "prima", "first": "prima",
@ -89,27 +247,13 @@
"payment_notification": "notifica di pagamento", "payment_notification": "notifica di pagamento",
"price": "prezzo", "price": "prezzo",
"q_mail": "q-mail", "q_mail": "q-mail",
"question": { "server": "server",
"new_user": "sei un nuovo utente?" "settings": "settings",
"sort": {
"by_member": "by member"
}, },
"save_options": { "supply": "supply",
"no_pinned_changes": "attualmente non hai modifiche nelle app fissate", "tags": "tags",
"overwrite_changes": "l'app non è riuscita a scaricare le tue app fissate salvate su QDN. Vuoi sovrascrivere le modifiche?",
"overwrite_qdn": "sovrascrivi su QDN",
"publish_qdn": "vuoi pubblicare le tue impostazioni su QDN (crittografate)?",
"qdn": "usa salvataggio QDN",
"register_name": "hai bisogno di un nome Qortal registrato per salvare le app fissate su QDN.",
"reset_pinned": "non ti piacciono le modifiche locali? Vuoi ripristinare le app fissate predefinite?",
"reset_qdn": "non ti piacciono le modifiche locali? Vuoi ripristinare le app fissate salvate su QDN?",
"revert_default": "ripristina ai valori predefiniti",
"revert_qdn": "ripristina da QDN",
"save_qdn": "salva su QDN",
"save": "salva",
"settings": "stai usando il metodo di esportazione/importazione per salvare le impostazioni.",
"unsaved_changes": "hai modifiche non salvate nelle app fissate. Salvale su QDN."
},
"settings": "impostazioni",
"supply": "offerta",
"theme": { "theme": {
"dark": "modalità scura", "dark": "modalità scura",
"light": "modalità chiara" "light": "modalità chiara"
@ -125,9 +269,13 @@
"title": "titolo", "title": "titolo",
"tutorial": "tutorial", "tutorial": "tutorial",
"user_lookup": "ricerca utente", "user_lookup": "ricerca utente",
"zip": "zip",
"wallet": { "wallet": {
"litecoin": "portafoglio litecoin",
"qortal": "portafoglio qortal",
"wallet": "portafoglio", "wallet": "portafoglio",
"wallet_other": "portafogli" "wallet_other": "portafogli"
}, },
"website": "website",
"welcome": "benvenuto" "welcome": "benvenuto"
} }

View File

@ -16,9 +16,17 @@
"load_members": "carica membri con nomi", "load_members": "carica membri con nomi",
"make_admin": "rendi amministratore", "make_admin": "rendi amministratore",
"manage_members": "gestisci membri", "manage_members": "gestisci membri",
"promote_group": "promuovi il gruppo ai non-membri",
"publish_announcement": "pubblica annuncio",
"publish_avatar": "pubblica avatar",
"refetch_page": "ricarica pagina", "refetch_page": "ricarica pagina",
"remove_admin": "rimuovi come amministratore", "remove_admin": "rimuovi come amministratore",
"return_to_thread": "torna alle discussioni" "remove_minting_account": "rimuovi account di conio",
"return_to_thread": "torna alle discussioni",
"scroll_bottom": "vai in fondo",
"scroll_unread_messages": "vai ai messaggi non letti",
"select_group": "seleziona un gruppo",
"visit_q_mintership": "visita Q-Mintership"
}, },
"advanced_options": "opzioni avanzate", "advanced_options": "opzioni avanzate",
"approval_threshold": "soglia di approvazione del gruppo (numero / percentuale di amministratori che devono approvare una transazione)", "approval_threshold": "soglia di approvazione del gruppo (numero / percentuale di amministratori che devono approvare una transazione)",
@ -28,14 +36,21 @@
"maximum": "ritardo massimo dei blocchi per le approvazioni delle transazioni di gruppo" "maximum": "ritardo massimo dei blocchi per le approvazioni delle transazioni di gruppo"
}, },
"group": { "group": {
"avatar": "avatar del gruppo",
"closed": "chiuso (privato) gli utenti hanno bisogno del permesso per entrare", "closed": "chiuso (privato) gli utenti hanno bisogno del permesso per entrare",
"description": "descrizione del gruppo", "description": "descrizione del gruppo",
"id": "ID gruppo", "id": "ID gruppo",
"invites": "inviti del gruppo", "invites": "inviti del gruppo",
"group": "gruppo",
"group_other": "gruppi",
"management": "gestione del gruppo", "management": "gestione del gruppo",
"member_number": "numero di membri", "member_number": "numero di membri",
"messaging": "messaggi",
"name": "nome del gruppo", "name": "nome del gruppo",
"open": "aperto (pubblico)", "open": "aperto (pubblico)",
"private": "gruppo privato",
"promotions": "promozione gruppi",
"public": "gruppo pubblico",
"type": "tipo di gruppo" "type": "tipo di gruppo"
}, },
"invitation_expiry": "tempo di scadenza dell'invito", "invitation_expiry": "tempo di scadenza dell'invito",
@ -46,16 +61,40 @@
"latest_mails": "ultimi Q-Mails", "latest_mails": "ultimi Q-Mails",
"message": { "message": {
"generic": { "generic": {
"avatar_publish_fee": "publishing an Avatar requires {{ fee }}",
"avatar_registered_name": "a registered name is required to set an avatar",
"admin_only": "only groups where you are an admin will be shown",
"already_in_group": "sei già in questo gruppo!", "already_in_group": "sei già in questo gruppo!",
"closed_group": "questo è un gruppo chiuso/privato, devi aspettare che un admin accetti la tua richiesta", "closed_group": "questo è un gruppo chiuso/privato, devi aspettare che un admin accetti la tua richiesta",
"descrypt_wallet": "decrittazione portafoglio in corso...", "descrypt_wallet": "decrittazione portafoglio in corso...",
"encryption_key": "la prima chiave di cifratura comune del gruppo è in fase di creazione. Attendere qualche minuto affinché venga recuperata dalla rete. Controllo ogni 2 minuti...", "encryption_key": "la prima chiave di cifratura comune del gruppo è in fase di creazione. Attendere qualche minuto affinché venga recuperata dalla rete. Controllo ogni 2 minuti...",
"group_announcement": "group Announcements",
"group_invited_you": "{{group}} ti ha invitato", "group_invited_you": "{{group}} ti ha invitato",
"group_key_created": "first group key created.",
"group_member_list_changed": "the group member list has changed. Please re-encrypt the secret key.",
"group_no_secret_key": "there is no group secret key. Be the first admin to publish one!",
"group_secret_key_no_owner": "the latest group secret key was published by a non-owner. As the owner of the group please re-encrypt the key as a safeguard.",
"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": "caricamento elenco membri con nomi... attendere.", "loading_members": "caricamento elenco membri con nomi... attendere.",
"max_chars": "max 200 characters. Publish Fee",
"manage_minting": "manage your minting",
"minter_group": "you are currently not part of the MINTER group",
"mintership_app": "visit the Q-Mintership app to apply to be a minter",
"minting_account": "minting account:",
"minting_keys_per_node": "only 2 minting keys are allowed per node. Please remove one if you would like to mint with this account.",
"minting_keys_per_node_different": "only 2 minting keys are allowed per node. Please remove one if you would like to add a different account.",
"next_level": "blocks remaining until next level:",
"node_minting": "This node is minting:",
"node_minting_account": "node's minting accounts",
"node_minting_key": "you currently have a minting key for this account attached to this node",
"no_announcement": "no announcements",
"no_display": "niente da mostrare", "no_display": "niente da mostrare",
"no_selection": "nessun gruppo selezionato", "no_selection": "nessun gruppo selezionato",
"not_part_group": "non fai parte del gruppo cifrato. Attendi che un amministratore re-critti le chiavi.", "not_part_group": "non fai parte del gruppo cifrato. Attendi che un amministratore re-critti le chiavi.",
"only_encrypted": "verranno mostrati solo i messaggi non cifrati.", "only_encrypted": "verranno mostrati solo i messaggi non cifrati.",
"only_private_groups": "only private groups will be shown",
"private_key_copied": "chiave privata copiata", "private_key_copied": "chiave privata copiata",
"provide_message": "inserisci un primo messaggio per la discussione", "provide_message": "inserisci un primo messaggio per la discussione",
"secure_place": "conserva la tua chiave privata in un luogo sicuro. Non condividerla!", "secure_place": "conserva la tua chiave privata in un luogo sicuro. Non condividerla!",
@ -66,10 +105,15 @@
"descrypt_wallet": "errore nella decrittazione del portafoglio {{ :errorMessage }}", "descrypt_wallet": "errore nella decrittazione del portafoglio {{ :errorMessage }}",
"description_required": "inserisci una descrizione", "description_required": "inserisci una descrizione",
"group_info": "impossibile accedere alle informazioni del gruppo", "group_info": "impossibile accedere alle informazioni del gruppo",
"group_promotion": "error publishing the promotion. Please try again",
"group_secret_key": "impossibile ottenere la chiave segreta del gruppo", "group_secret_key": "impossibile ottenere la chiave segreta del gruppo",
"name_required": "inserisci un nome", "name_required": "inserisci un nome",
"notify_admins": "prova a contattare un amministratore dalla lista qui sotto:", "notify_admins": "prova a contattare un amministratore dalla lista qui sotto:",
"thread_id": "impossibile trovare l'ID della discussione" "qortals_required": "you need at least {{ quantity }} QORT to send a message",
"timeout_reward": "timeout waiting for reward share confirmation",
"thread_id": "impossibile trovare l'ID della discussione",
"unable_determine_group_private": "unable to determine if group is private",
"unable_minting": "unable to start minting"
}, },
"success": { "success": {
"group_ban": "membro bannato con successo dal gruppo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino", "group_ban": "membro bannato con successo dal gruppo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
@ -87,19 +131,27 @@
"group_leave_name": "uscito dal gruppo {{group_name}}: in attesa di conferma", "group_leave_name": "uscito dal gruppo {{group_name}}: in attesa di conferma",
"group_leave_label": "uscito dal gruppo {{name}}: successo!", "group_leave_label": "uscito dal gruppo {{name}}: successo!",
"group_member_admin": "membro nominato amministratore con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino", "group_member_admin": "membro nominato amministratore con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"group_promotion": "successfully published promotion. It may take a couple of minutes for the promotion to appear",
"group_remove_member": "amministratore rimosso con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino", "group_remove_member": "amministratore rimosso con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"invitation_cancellation": "invito annullato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino", "invitation_cancellation": "invito annullato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"invitation_request": "richiesta di adesione accettata: in attesa di conferma", "invitation_request": "richiesta di adesione accettata: in attesa di conferma",
"loading_threads": "caricamento discussioni... attendere.", "loading_threads": "caricamento discussioni... attendere.",
"post_creation": "post creato con successo. Potrebbe volerci un po' per la pubblicazione", "post_creation": "post creato con successo. Potrebbe volerci un po' per la pubblicazione",
"published_secret_key": "published secret key for group {{ group_id }}: awaiting confirmation",
"published_secret_key_label": "published secret key for group {{ group_id }}: success!",
"registered_name": "successfully registered. It may take a couple of minutes for the changes to propagate",
"registered_name_label": "registered name: awaiting confirmation. This may take a couple minutes.",
"registered_name_success": "registered name: success!",
"rewardshare_add": "add rewardshare: awaiting confirmation",
"rewardshare_add_label": "add rewardshare: success!",
"rewardshare_creation": "confirming creation of rewardshare on chain. Please be patient, this could take up to 90 seconds.",
"rewardshare_confirmed": "rewardshare confirmed. Please click Next.",
"rewardshare_remove": "remove rewardshare: awaiting confirmation",
"rewardshare_remove_label": "remove rewardshare: success!",
"thread_creation": "discussione creata con successo. Potrebbe volerci un po' per la pubblicazione", "thread_creation": "discussione creata con successo. Potrebbe volerci un po' per la pubblicazione",
"unbanned_user": "utente sbannato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino", "unbanned_user": "utente sbannato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"user_joined": "utente entrato con successo!" "user_joined": "utente entrato con successo!"
} }
}, },
"question": {
"perform_transaction": "vuoi eseguire una transazione di tipo {{action}}?",
"provide_thread": "inserisci un titolo per la discussione"
},
"thread_posts": "nuovi messaggi nella discussione" "thread_posts": "nuovi messaggi nella discussione"
} }

View File

@ -1,10 +1,28 @@
{ {
"account": { "account": {
"your": "Ваш аккаунт", "your": "ваш аккаунт",
"account_many": "аккаунты", "account_many": "аккаунты",
"account_one": "аккаунт" "account_one": "аккаунт",
"selected": "выбранный аккаунт"
}, },
"advanced_users": "для продвинутых пользователей", "action": {
"add": {
"account": "добавить аккаунт",
"seed_phrase": "добавить seed-фразу"
},
"authenticate": "аутентификация",
"create_account": "создать аккаунт",
"create_qortal_account": "создайте свой аккаунт Qortal, нажав <next>ДАЛЕЕ</next> ниже.",
"choose_password": "выберите новый пароль",
"download_account": "скачать аккаунт",
"export_seedphrase": "экспортировать seed-фразу",
"publish_admin_secret_key": "опубликовать секретный ключ администратора",
"publish_group_secret_key": "опубликовать секретный ключ группы",
"reencrypt_key": "перешифровать ключ",
"return_to_list": "вернуться к списку",
"setup_qortal_account": "настройте ваш аккаунт Qortal"
},
"advanced_users": "для опытных пользователей",
"apikey": { "apikey": {
"alternative": "альтернатива: выбрать файл", "alternative": "альтернатива: выбрать файл",
"change": "изменить API-ключ", "change": "изменить API-ключ",
@ -13,11 +31,33 @@
"key": "API-ключ", "key": "API-ключ",
"select_valid": "выберите действительный API-ключ" "select_valid": "выберите действительный API-ключ"
}, },
"authenticate": "аутентификация",
"build_version": "версия сборки", "build_version": "версия сборки",
"create_account": "создать аккаунт", "message": {
"download_account": "скачать аккаунт", "error": {
"keep_secure": "Храните файл аккаунта в безопасности", "account_creation": "не удалось создать аккаунт.",
"field_not_found_json": "{{ field }} не найден в JSON",
"incorrect_password": "неверный пароль",
"invalid_secret_key": "некорректный секретный ключ",
"unable_reencrypt_secret_key": "не удалось перешифровать секретный ключ"
},
"generic": {
"congrats_setup": "поздравляем, всё готово!",
"no_account": "аккаунты не сохранены",
"no_minimum_length": "требование к минимальной длине отсутствует",
"no_secret_key_published": "секретный ключ еще не опубликован",
"fetching_admin_secret_key": "получение секретного ключа администратора",
"fetching_group_secret_key": "публикация секретного ключа группы",
"last_encryption_date": "дата последнего шифрования: {{ date }}, автор: {{ name }}",
"keep_secure": "храните файл аккаунта в безопасности",
"publishing_key": "напоминание: после публикации ключа может пройти несколько минут до его появления. Пожалуйста, подождите.",
"seedphrase_notice": "в фоновом режиме была случайным образом сгенерирована <seed>SEED-ФРАЗА</seed>.",
"type_seed": "введите или вставьте свою seed-фразу",
"your_accounts": "ваши сохранённые аккаунты"
},
"success": {
"reencrypted_secret_key": "секретный ключ успешно перешифрован. Обновления могут занять несколько минут. Обновите группу через 5 минут."
}
},
"node": { "node": {
"choose": "выбрать пользовательский узел", "choose": "выбрать пользовательский узел",
"custom_many": "пользовательские узлы", "custom_many": "пользовательские узлы",
@ -26,17 +66,31 @@
"using": "используется узел", "using": "используется узел",
"using_public": "используется публичный узел" "using_public": "используется публичный узел"
}, },
"note": "заметка",
"password": "пароль", "password": "пароль",
"password_confirmation": "подтвердите пароль", "password_confirmation": "подтвердите пароль",
"return_to_list": "вернуться к списку", "seed": "seed-фраза",
"seed_your": "ваша seed-фраза",
"tips": {
"additional_wallet": "используйте эту опцию, чтобы подключить дополнительные кошельки Qortal, которые вы уже создали. Вам понадобится файл резервной копии JSON.",
"digital_id": "ваш кошелёк — это ваш цифровой идентификатор в Qortal. Через него вы входите в интерфейс пользователя Qortal. Он содержит ваш публичный адрес и выбранное вами имя Qortal. Все ваши транзакции связаны с этим ID. Здесь вы управляете QORT и другими криптовалютами в Qortal.",
"existing_account": "уже есть аккаунт Qortal? Введите здесь вашу секретную фразу восстановления для доступа. Это один из способов восстановить аккаунт.",
"key_encrypt_admin": "этот ключ используется для шифрования контента, связанного с АДМИНИСТРАТОРОМ. Только администраторы смогут его видеть.",
"key_encrypt_group": "этот ключ используется для шифрования контента ГРУППЫ. В данный момент он единственный, используемый в этом интерфейсе. Все участники группы смогут видеть зашифрованный контент.",
"new_account": "создание аккаунта создаёт новый кошелёк и цифровой ID для начала работы с Qortal. После этого вы сможете получать QORT, покупать имя и аватар, публиковать видео и блоги и многое другое.",
"new_users": "новые пользователи — начните отсюда!",
"safe_place": "сохраните свой аккаунт в надёжном месте, которое вы не забудете!",
"view_seedphrase": "если вы хотите ПОСМОТРЕТЬ SEED-ФРАЗУ, нажмите на слово 'SEED-ФРАЗА' в этом тексте. Seed-фразы используются для генерации приватного ключа аккаунта. В целях безопасности по умолчанию они НЕ отображаются.",
"wallet_secure": "держите файл кошелька в безопасности."
},
"wallet": { "wallet": {
"password_confirmation": "подтвердите пароль кошелька", "password_confirmation": "подтвердите пароль кошелька",
"password": "пароль кошелька", "password": "пароль кошелька",
"keep_password": "сохранить текущий пароль", "keep_password": "оставить текущий пароль",
"new_password": "новый пароль", "new_password": "новый пароль",
"error": { "error": {
"missing_new_password": "пожалуйста, введите новый пароль", "missing_new_password": "введите новый пароль",
"missing_password": "пожалуйста, введите ваш пароль" "missing_password": "введите пароль"
} }
}, },
"welcome": "добро пожаловать в" "welcome": "добро пожаловать в"

File diff suppressed because it is too large Load Diff

View File

@ -56,7 +56,6 @@ import { QORT_DECIMALS } from '../constants/constants';
import Base58 from '../deps/Base58'; import Base58 from '../deps/Base58';
import ed2curve from '../deps/ed2curve'; import ed2curve from '../deps/ed2curve';
import nacl from '../deps/nacl-fast'; import nacl from '../deps/nacl-fast';
import { import {
base64ToUint8Array, base64ToUint8Array,
createSymmetricKeyAndNonce, createSymmetricKeyAndNonce,
@ -87,7 +86,7 @@ import { RequestQueueWithPromise } from '../utils/queue/queue';
import utils from '../utils/utils'; import utils from '../utils/utils';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
import { isValidBase64WithDecode } from '../utils/decode'; import { isValidBase64WithDecode } from '../utils/decode';
//TODO translate
const uid = new ShortUniqueId({ length: 6 }); const uid = new ShortUniqueId({ length: 6 });
export const requestQueueGetAtAddresses = new RequestQueueWithPromise(10); export const requestQueueGetAtAddresses = new RequestQueueWithPromise(10);
@ -133,6 +132,7 @@ export async function retryTransaction(
throwError, throwError,
retries = MAX_RETRIES retries = MAX_RETRIES
) { ) {
// TODO transalte
let attempt = 0; let attempt = 0;
while (attempt < retries) { while (attempt < retries) {
try { try {

View File

@ -228,5 +228,5 @@ export const CustomLabel = styled(InputLabel)(({ theme }) => ({
fontFamily: 'Inter', fontFamily: 'Inter',
fontSize: '15px', fontSize: '15px',
fontWeight: 400, fontWeight: 400,
lineHeight: '12px', lineHeight: '24px',
})); }));

Some files were not shown because too many files have changed in this diff Show More