Merge pull request #54 from nbenaglia/feature/i18n-other-minor-files

i18n: Add translations for minor files
This commit is contained in:
nico.benaz 2025-05-20 21:45:35 +02:00 committed by GitHub
commit 4cb66a1193
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 774 additions and 326 deletions

3
.gitignore vendored
View File

@ -25,3 +25,6 @@ dist-ssr
*.sw? *.sw?
release-builds/ release-builds/
.env .env
# reports from scripts
scripts/i18n_report*

View File

@ -20,6 +20,20 @@ Translation in GUI:
- For all translation in uppercase `{ postProcess: 'capitalizeAll' }` - For all translation in uppercase `{ postProcess: 'capitalizeAll' }`
- See `.src/i18n/i18n.ts` for processor definition - See `.src/i18n/i18n.ts` for processor definition
## Namespace
These are the current namespaces, in which all translations are organized:
- `auth`: relative to the authentication (name, addresses, keys, secrets, seedphrase, and so on...)
- `core`: all the core translation
- `group`: all translations concerning group management
- `tutorial`: dedicated to the tutorial pages
Please avoid duplication of the same translation.
In the same page the usage of translations from different namespaces is permissible.
## Missing language? ## Missing language?
- Please open an issue on the project's github repository and specify the missing language - Please open an issue on the project's github repository and specify the missing language, by clicking [here](https://github.com/Qortal/Qortal-Hub/issues/new)
- You can also open a Pull Request if you like to contribute directly to the project.

108
scripts/i18n-checker.py Normal file
View File

@ -0,0 +1,108 @@
import os
import re
import json
import csv
import argparse
# Customize as needed
I18N_FUNCTIONS = ['t', 'i18next.t']
FILE_EXTENSIONS = ['.tsx']
EXCLUDED_DIRS = ['node_modules', 'build', 'dist']
# Regex patterns
STRING_LITERAL_REGEX = re.compile(r'(?<!t\()\s*["\']([A-Z][^"\']{2,})["\']')
JSX_TEXT_REGEX = re.compile(r'>\s*([A-Z][a-z].*?)\s*<')
def is_excluded(path):
return any(excluded in path for excluded in EXCLUDED_DIRS)
def is_ignorable(text):
return (
re.fullmatch(r'[A-Z0-9_]+', text) and
any(keyword in text.lower() for keyword in ['action', 'status'])
)
def is_console_log_line(line):
return any(kw in line for kw in ['console.log', 'console.error', 'console.warn'])
def find_untranslated_strings(file_path):
issues = []
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
lines = content.splitlines()
for idx, line in enumerate(lines, start=1):
if is_console_log_line(line):
continue # Skip entire line if it's a console log statement
# Match suspicious string literals
for match in STRING_LITERAL_REGEX.finditer(line):
string = match.group(1).strip()
if is_ignorable(string):
continue
if not any(fn + '(' in line[:match.start()] for fn in I18N_FUNCTIONS):
issues.append({
'file': file_path,
'line': idx,
'type': 'StringLiteral',
'text': string
})
# Match JSX text nodes
for match in JSX_TEXT_REGEX.finditer(line):
text = match.group(1).strip()
if is_ignorable(text):
continue
if not text.startswith('{t('):
issues.append({
'file': file_path,
'line': idx,
'type': 'JSXText',
'text': text
})
return issues
def scan_directory(directory):
all_issues = []
for root, _, files in os.walk(directory):
if is_excluded(root):
continue
for file in files:
if any(file.endswith(ext) for ext in FILE_EXTENSIONS):
file_path = os.path.join(root, file)
issues = find_untranslated_strings(file_path)
all_issues.extend(issues)
return all_issues
def save_report(results, output_file):
_, ext = os.path.splitext(output_file)
if ext.lower() == '.json':
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(results, f, indent=2)
elif ext.lower() == '.csv':
with open(output_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['file', 'line', 'type', 'text'])
writer.writeheader()
for row in results:
writer.writerow(row)
else:
raise ValueError("Unsupported output format. Use .json or .csv")
def main():
parser = argparse.ArgumentParser(description='Detect untranslated strings in React (.tsx) files.')
parser.add_argument('-path', default='../src/', help='Path to the source directory (e.g. ./src)')
parser.add_argument('-o', '--output', default='./i18n_report.json', help='Report output file (.json or .csv)')
args = parser.parse_args()
results = scan_directory(args.path)
if results:
save_report(results, args.output)
print(f"⚠️ Found {len(results)} potential untranslated strings. Report saved to {args.output}")
else:
print("✅ No obvious untranslated strings found.")
if __name__ == "__main__":
main()

View File

@ -1697,10 +1697,11 @@ function App() {
style={{ style={{
fontSize: '14px', fontSize: '14px',
fontWeight: 700, fontWeight: 700,
textTransform: 'uppercase',
}} }}
> >
{t('core:user_lookup')} {t('core:user_lookup', {
postProcess: 'capitalizeAll',
})}
</span> </span>
} }
placement="left" placement="left"
@ -2334,7 +2335,7 @@ function App() {
hostname: requestBuyOrder?.hostname, hostname: requestBuyOrder?.hostname,
count: requestBuyOrder?.crosschainAtInfo?.length || 0, count: requestBuyOrder?.crosschainAtInfo?.length || 0,
}} }}
tOptions={{ postProcess: ['capitalizeFirst'] }} tOptions={{ postProcess: ['capitalizeFirstChar'] }}
> >
The Application <br /> The Application <br />
<italic>{{ hostname }}</italic> <br /> <italic>{{ hostname }}</italic> <br />
@ -2445,7 +2446,7 @@ function App() {
hostname: requestBuyOrder?.hostname, hostname: requestBuyOrder?.hostname,
count: requestBuyOrder?.crosschainAtInfo?.length || 0, count: requestBuyOrder?.crosschainAtInfo?.length || 0,
}} }}
tOptions={{ postProcess: ['capitalizeFirst'] }} tOptions={{ postProcess: ['capitalizeFirstChar'] }}
> >
The Application <br /> The Application <br />
<italic>{{ hostname }}</italic> <br /> <italic>{{ hostname }}</italic> <br />
@ -2927,7 +2928,7 @@ function App() {
/> />
), ),
}} }}
tOptions={{ postProcess: ['capitalizeFirst'] }} tOptions={{ postProcess: ['capitalizeFirstChar'] }}
> >
A <seed>SEEDPHRASE</seed> has been randomly generated in A <seed>SEEDPHRASE</seed> has been randomly generated in
the background. the background.
@ -2964,7 +2965,7 @@ function App() {
/> />
), ),
}} }}
tOptions={{ postProcess: ['capitalizeFirst'] }} tOptions={{ postProcess: ['capitalizeFirstChar'] }}
> >
Create your Qortal account by clicking <next>NEXT</next>{' '} Create your Qortal account by clicking <next>NEXT</next>{' '}
below. below.

View File

@ -367,6 +367,7 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
}} }}
/> />
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
sx={{ sx={{
'& .MuiTypography-root': { '& .MuiTypography-root': {
@ -377,7 +378,7 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
: theme.palette.text.primary, : theme.palette.text.primary,
}, },
}} }}
primary={`${ primary={
isSelectedAppPinned isSelectedAppPinned
? t('core:action.unpin_app', { ? t('core:action.unpin_app', {
postProcess: 'capitalizeFirstChar', postProcess: 'capitalizeFirstChar',
@ -385,7 +386,7 @@ export const AppsNavBarDesktop = ({ disableBack }) => {
: t('core:action.pin_app', { : t('core:action.pin_app', {
postProcess: 'capitalizeFirstChar', postProcess: 'capitalizeFirstChar',
}) })
}}`} }
/> />
</MenuItem> </MenuItem>

View File

@ -40,6 +40,7 @@ const TabComponent = ({ isSelected, app }) => {
}} }}
/> />
)} )}
{app?.isPrivate && !app?.privateAppProperties?.logo ? ( {app?.isPrivate && !app?.privateAppProperties?.logo ? (
<LockIcon <LockIcon
sx={{ sx={{

View File

@ -249,6 +249,7 @@ export const GroupAvatar = ({
); );
}; };
// TODO the following part is the same as in MainAvatar.tsx
const PopoverComp = ({ const PopoverComp = ({
avatarFile, avatarFile,
setAvatarFile, setAvatarFile,

View File

@ -34,20 +34,20 @@ export const DesktopSideBar = ({
<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
sx={{ sx={{
width: '70px',
height: '70px', height: '70px',
paddingTop: '23px', paddingTop: '23px',
width: '70px',
}} }}
> >
<CoreSyncStatus /> <CoreSyncStatus />
@ -55,8 +55,8 @@ export const DesktopSideBar = ({
<ButtonBase <ButtonBase
sx={{ sx={{
width: '60px',
height: '60px', height: '60px',
width: '60px',
}} }}
onClick={() => { onClick={() => {
goToHome(); goToHome();

View File

@ -215,7 +215,9 @@ export const PollCard = ({
setIsOpen(true); setIsOpen(true);
}} }}
> >
{t('core:action.show_poll', { postProcess: 'capitalizeFirst' })} {t('core:action.show_poll', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
</> </>
)} )}
@ -273,7 +275,7 @@ export const PollCard = ({
fontSize: '18px', fontSize: '18px',
}} }}
> >
{t('core:option_other', { postProcess: 'capitalizeFirst' })} {t('core:option_other', { postProcess: 'capitalizeFirstChar' })}
</Typography> </Typography>
<RadioGroup <RadioGroup
@ -308,7 +310,7 @@ export const PollCard = ({
disabled={!selectedOption || isLoadingSubmit} disabled={!selectedOption || isLoadingSubmit}
onClick={handleVote} onClick={handleVote}
> >
{t('core:action.vote', { postProcess: 'capitalizeFirst' })} {t('core:action.vote', { postProcess: 'capitalizeFirstChar' })}
</Button> </Button>
<Typography <Typography
@ -337,7 +339,7 @@ export const PollCard = ({
}} }}
> >
{t('core:message.generic.already_voted', { {t('core:message.generic.already_voted', {
postProcess: 'capitalizeFirst', postProcess: 'capitalizeFirstChar',
})} })}
</Typography> </Typography>
@ -350,7 +352,7 @@ export const PollCard = ({
}} }}
> >
{t('core:message.generic.processing_transaction', { {t('core:message.generic.processing_transaction', {
postProcess: 'capitalizeFirst', postProcess: 'capitalizeFirstChar',
})} })}
</Typography> </Typography>
)} )}
@ -361,8 +363,8 @@ export const PollCard = ({
}} }}
> >
{showResults {showResults
? t('core:action.hide', { postProcess: 'capitalizeFirst' }) ? t('core:action.hide', { postProcess: 'capitalizeFirstChar' })
: t('core:action.close', { postProcess: 'capitalizeFirst' })} : t('core:action.close', { postProcess: 'capitalizeFirstChar' })}
</ButtonBase> </ButtonBase>
</CardContent> </CardContent>

View File

@ -67,7 +67,7 @@ interface VideoPlayerProps {
user?: string; user?: string;
} }
// TODO translate and theme? Is it worth? // TODO translate and theme (optional)
export const VideoPlayer: FC<VideoPlayerProps> = ({ export const VideoPlayer: FC<VideoPlayerProps> = ({
poster, poster,
name, name,

View File

@ -593,6 +593,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
</Box> </Box>
</Box> </Box>
)} )}
{value === 1 && ( {value === 1 && (
<Box <Box
sx={{ sx={{

View File

@ -41,9 +41,7 @@ const cache = new CellMeasurerCache({
export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
const [memberGroups] = useAtom(memberGroupsAtom); const [memberGroups] = useAtom(memberGroupsAtom);
const setTxList = useSetAtom(txListAtom); const setTxList = useSetAtom(txListAtom);
const { t } = useTranslation(['auth', 'core', 'group']); const { t } = useTranslation(['auth', 'core', 'group']);
const [groups, setGroups] = useState([]); const [groups, setGroups] = useState([]);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
@ -189,7 +187,11 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
.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: 'capitalizeFirstChar',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -248,10 +250,14 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
})}{' '} })}{' '}
{group?.groupName} {group?.groupName}
</Typography> </Typography>
<Typography> <Typography>
{group?.isOpen === false && {group?.isOpen === false &&
'This is a closed/private group, so you will need to wait until an admin accepts your request'} t('group:message.generic.closed_group', {
postProcess: 'capitalizeFirstChar',
})}
</Typography> </Typography>
<LoadingButton <LoadingButton
loading={isLoading} loading={isLoading}
loadingPosition="start" loadingPosition="start"
@ -264,6 +270,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>
<ListItemButton <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)} onClick={(event) => handlePopoverOpen(event, index)}
> >
@ -274,6 +281,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
}} }}
/> />
)} )}
{group?.isOpen === true && ( {group?.isOpen === true && (
<NoEncryptionGmailerrorredIcon <NoEncryptionGmailerrorredIcon
sx={{ sx={{
@ -281,7 +289,9 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
}} }}
/> />
)} )}
<Spacer width="15px" /> <Spacer width="15px" />
<ListItemText <ListItemText
primary={group?.groupName} primary={group?.groupName}
secondary={group?.description} secondary={group?.description}

View File

@ -24,13 +24,14 @@ import { useModal } from '../../common/useModal';
import { isOpenBlockedModalAtom } from '../../atoms/global'; import { isOpenBlockedModalAtom } from '../../atoms/global';
import InfoIcon from '@mui/icons-material/Info'; import InfoIcon from '@mui/icons-material/Info';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
export const BlockedUsersModal = () => { export const BlockedUsersModal = () => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const [isOpenBlockedModal, setIsOpenBlockedModal] = useAtom( const [isOpenBlockedModal, setIsOpenBlockedModal] = useAtom(
isOpenBlockedModalAtom isOpenBlockedModalAtom
); );
const [hasChanged, setHasChanged] = useState(false); const [hasChanged, setHasChanged] = useState(false);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [addressesWithNames, setAddressesWithNames] = useState({}); const [addressesWithNames, setAddressesWithNames] = useState({});
@ -95,7 +96,12 @@ export const BlockedUsersModal = () => {
if (!isAddress) { if (!isAddress) {
const response = await fetch(`${getBaseApiReact()}/names/${valUser}`); const response = await fetch(`${getBaseApiReact()}/names/${valUser}`);
const data = await response.json(); const data = await response.json();
if (!data?.owner) throw new Error('Name does not exist'); if (!data?.owner)
throw new Error(
t('auth:message.error.name_not_existing', {
postProcess: 'capitalizeFirstChar',
})
);
if (data?.owner) { if (data?.owner) {
userAddress = data.owner; userAddress = data.owner;
userName = valUser; userName = valUser;
@ -133,7 +139,11 @@ export const BlockedUsersModal = () => {
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: error?.message || 'Unable to block user', message:
error?.message ||
t('auth:message.error.unable_block_user', {
postProcess: 'capitalizeFirstChar',
}),
}); });
} }
}; };
@ -161,7 +171,9 @@ export const BlockedUsersModal = () => {
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle>Blocked Users</DialogTitle> <DialogTitle>
{t('auth:blocked_users', { postProcess: 'capitalizeFirstChar' })}
</DialogTitle>
<DialogContent <DialogContent
sx={{ sx={{
padding: '20px', padding: '20px',
@ -188,20 +200,28 @@ export const BlockedUsersModal = () => {
variant="contained" variant="contained"
onClick={blockUser} onClick={blockUser}
> >
Block {t('auth:action.block', { postProcess: 'capitalizeFirstChar' })}
</Button> </Button>
</Box> </Box>
{Object.entries(blockedUsers?.addresses).length > 0 && ( {Object.entries(blockedUsers?.addresses).length > 0 && (
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Blocked addresses- blocks processing of txs {t('auth:message.generic.blocked_addresses', {
postProcess: 'capitalizeFirstChar',
})}
</DialogContentText> </DialogContentText>
<Spacer height="10px" /> <Spacer height="10px" />
<Button variant="contained" size="small" onClick={getNames}> <Button variant="contained" size="small" onClick={getNames}>
Fetch names {t('auth:action.fetch_names', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
<Spacer height="10px" /> <Spacer height="10px" />
</> </>
)} )}
@ -243,19 +263,26 @@ export const BlockedUsersModal = () => {
} }
}} }}
> >
Unblock {t('auth:action.unblock', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
</Box> </Box>
); );
} }
)} )}
</Box> </Box>
{Object.entries(blockedUsers?.names).length > 0 && ( {Object.entries(blockedUsers?.names).length > 0 && (
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Blocked names for QDN {t('core:message.generic.blocked_names', {
postProcess: 'capitalizeFirstChar',
})}
</DialogContentText> </DialogContentText>
<Spacer height="10px" /> <Spacer height="10px" />
</> </>
)} )}
@ -279,6 +306,7 @@ export const BlockedUsersModal = () => {
}} }}
> >
<Typography>{key}</Typography> <Typography>{key}</Typography>
<Button <Button
size="small" size="small"
sx={{ sx={{
@ -295,13 +323,16 @@ export const BlockedUsersModal = () => {
} }
}} }}
> >
Unblock {t('auth:action.unblock', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
</Box> </Box>
); );
})} })}
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
sx={{ sx={{
@ -323,7 +354,7 @@ export const BlockedUsersModal = () => {
setIsOpenBlockedModal(false); setIsOpenBlockedModal(false);
}} }}
> >
close {t('core:action.close', { postProcess: 'capitalizeFirstChar' })}
</Button> </Button>
</DialogActions> </DialogActions>
@ -333,13 +364,19 @@ export const BlockedUsersModal = () => {
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle id="alert-dialog-title"> <DialogTitle id="alert-dialog-title">
{'Decide what to block'} {t('auth:message.generic.decide_block', {
postProcess: 'capitalizeFirstChar',
})}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Blocking {message?.userName || message?.userAddress} {t('auth:message.generic.blocking', {
name: message?.userName || message?.userAddress,
postProcess: 'capitalizeFirstChar',
})}
</DialogContentText> </DialogContentText>
<Box <Box
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@ -354,7 +391,9 @@ export const BlockedUsersModal = () => {
}} }}
/>{' '} />{' '}
<Typography> <Typography>
Choose "block txs" or "all" to block chat messages{' '} {t('auth:message.generic.choose_block', {
postProcess: 'capitalizeFirstChar',
})}
</Typography> </Typography>
</Box> </Box>
</DialogContent> </DialogContent>
@ -366,7 +405,7 @@ export const BlockedUsersModal = () => {
onOk('address'); onOk('address');
}} }}
> >
Block txs {t('auth:action.block_txs', { postProcess: 'capitalizeFirstChar' })}
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
@ -374,7 +413,9 @@ export const BlockedUsersModal = () => {
onOk('name'); onOk('name');
}} }}
> >
Block QDN data {t('auth:action.block_data', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
@ -382,7 +423,7 @@ export const BlockedUsersModal = () => {
onOk('both'); onOk('both');
}} }}
> >
Block All {t('auth:action.block_all', { postProcess: 'capitalizeFirstChar' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -271,6 +271,7 @@ export const GroupMail = ({
}, },
[allThreads, isPrivate] [allThreads, isPrivate]
); );
const getMailMessages = useCallback( const getMailMessages = useCallback(
async (groupId: string, members: any) => { async (groupId: string, members: any) => {
try { try {
@ -385,7 +386,6 @@ export const GroupMail = ({
}, [getMailMessages, groupId, members, secretKey, isPrivate]); }, [getMailMessages, groupId, members, secretKey, isPrivate]);
const interval = useRef<any>(null); const interval = useRef<any>(null);
const firstMount = useRef(false); const firstMount = useRef(false);
const filterModeRef = useRef(''); const filterModeRef = useRef('');
@ -575,6 +575,7 @@ export const GroupMail = ({
}} }}
> >
<InstanceListHeader /> <InstanceListHeader />
<InstanceListContainer> <InstanceListContainer>
{filterOptions?.map((filter) => { {filterOptions?.map((filter) => {
return ( return (
@ -796,6 +797,7 @@ export const GroupMail = ({
postProcess: 'capitalizeFirstChar', postProcess: 'capitalizeFirstChar',
})} })}
</Typography> </Typography>
<ArrowForwardIosIcon <ArrowForwardIosIcon
sx={{ sx={{
color: theme.palette.text.primary, color: theme.palette.text.primary,

View File

@ -13,7 +13,6 @@ import {
NewMessageSendButton, NewMessageSendButton,
NewMessageSendP, NewMessageSendP,
} from './Mail-styles'; } from './Mail-styles';
import { ReusableModal } from './ReusableModal'; import { ReusableModal } from './ReusableModal';
import { Spacer } from '../../../common/Spacer'; import { Spacer } from '../../../common/Spacer';
import { CreateThreadIcon } from '../../../assets/Icons/CreateThreadIcon'; import { CreateThreadIcon } from '../../../assets/Icons/CreateThreadIcon';

View File

@ -29,10 +29,10 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
return ( return (
<SingleTheadMessageParent <SingleTheadMessageParent
sx={{ sx={{
height: 'auto',
alignItems: 'flex-start', alignItems: 'flex-start',
cursor: 'default',
borderRadius: '35px 4px 4px 4px', borderRadius: '35px 4px 4px 4px',
cursor: 'default',
height: 'auto',
}} }}
> >
<Box <Box
@ -80,6 +80,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
{formatTimestampForum(message?.created)} {formatTimestampForum(message?.created)}
</ThreadInfoColumnTime> </ThreadInfoColumnTime>
</ThreadInfoColumn> </ThreadInfoColumn>
<div <div
style={{ style={{
display: 'flex', display: 'flex',

View File

@ -278,12 +278,14 @@ export const Thread = ({
const urlNewer = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=1&includemetadata=false&reverse=false&prefix=true&before=${ const urlNewer = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=1&includemetadata=false&reverse=false&prefix=true&before=${
fullArrayMsg[0].created fullArrayMsg[0].created
}`; }`;
const responseNewer = await fetch(urlNewer, { const responseNewer = await fetch(urlNewer, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); });
const responseDataNewer = await responseNewer.json(); const responseDataNewer = await responseNewer.json();
if (responseDataNewer.length > 0) { if (responseDataNewer.length > 0) {
setHasFirstPage(true); setHasFirstPage(true);
@ -296,12 +298,14 @@ export const Thread = ({
const urlOlder = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=1&includemetadata=false&reverse=false&prefix=true&after=${ const urlOlder = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=${threadIdentifier}&identifier=${identifier}&limit=1&includemetadata=false&reverse=false&prefix=true&after=${
fullArrayMsg[fullArrayMsg.length - 1].created fullArrayMsg[fullArrayMsg.length - 1].created
}`; }`;
const responseOlder = await fetch(urlOlder, { const responseOlder = await fetch(urlOlder, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); });
const responseDataOlder = await responseOlder.json(); const responseDataOlder = await responseOlder.json();
if (responseDataOlder.length > 0) { if (responseDataOlder.length > 0) {
setHasLastPage(true); setHasLastPage(true);
@ -321,6 +325,7 @@ export const Thread = ({
}, },
[messages, secretKey] [messages, secretKey]
); );
const getMessages = useCallback(async () => { const getMessages = useCallback(async () => {
if ( if (
!currentThread || !currentThread ||
@ -337,6 +342,7 @@ export const Thread = ({
groupInfo?.groupId, groupInfo?.groupId,
isPrivate, isPrivate,
]); ]);
const firstMount = useRef(false); const firstMount = useRef(false);
const saveTimestamp = useCallback((currentThread: any, username?: string) => { const saveTimestamp = useCallback((currentThread: any, username?: string) => {
@ -613,6 +619,7 @@ export const Thread = ({
})} })}
</ComposeP> </ComposeP>
</ShowMessageReturnButton> </ShowMessageReturnButton>
{/* Conditionally render the scroll buttons */} {/* Conditionally render the scroll buttons */}
{showScrollButton && {showScrollButton &&
(isAtBottom ? ( (isAtBottom ? (

View File

@ -2388,6 +2388,7 @@ export const Group = ({
: 'flex', : 'flex',
}} }}
></AuthenticatedContainerInnerRight> ></AuthenticatedContainerInnerRight>
<LoadingSnackbar <LoadingSnackbar
open={isLoadingGroup} open={isLoadingGroup}
info={{ info={{

View File

@ -17,8 +17,9 @@ import { useTranslation } from 'react-i18next';
export const GroupInvites = ({ myAddress, setOpenAddGroup }) => { export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]); const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]);
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const { t } = useTranslation(['auth', 'core', 'group']);
const theme = useTheme();
const getJoinRequests = async () => { const getJoinRequests = async () => {
try { try {
@ -37,9 +38,6 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
} }
}; };
const { t } = useTranslation(['auth', 'core', 'group']);
const theme = useTheme();
useEffect(() => { useEffect(() => {
if (myAddress) { if (myAddress) {
getJoinRequests(); getJoinRequests();
@ -75,6 +73,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
{groupsWithJoinRequests?.length > 0 && {groupsWithJoinRequests?.length > 0 &&
` (${groupsWithJoinRequests?.length})`} ` (${groupsWithJoinRequests?.length})`}
</Typography> </Typography>
{isExpanded ? ( {isExpanded ? (
<ExpandLessIcon <ExpandLessIcon
sx={{ sx={{
@ -113,6 +112,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
<CustomLoader /> <CustomLoader />
</Box> </Box>
)} )}
{!loading && groupsWithJoinRequests.length === 0 && ( {!loading && groupsWithJoinRequests.length === 0 && (
<Box <Box
sx={{ sx={{
@ -136,6 +136,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
</Typography> </Typography>
</Box> </Box>
)} )}
<List <List
sx={{ sx={{
width: '100%', width: '100%',

View File

@ -1,4 +1,4 @@
import * as React from 'react'; import { useEffect, useMemo, 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 ListItemButton from '@mui/material/ListItemButton'; import ListItemButton from '@mui/material/ListItemButton';
@ -27,12 +27,10 @@ export const GroupJoinRequests = ({
setMobileViewMode, setMobileViewMode,
setDesktopViewMode, setDesktopViewMode,
}) => { }) => {
const [isExpanded, setIsExpanded] = React.useState(false); const [isExpanded, setIsExpanded] = useState(false);
const { t } = useTranslation(['auth', 'core', 'group']); const { t } = useTranslation(['auth', 'core', 'group']);
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState( const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]);
[] const [loading, setLoading] = useState(true);
);
const [loading, setLoading] = React.useState(true);
const [txList] = useAtom(txListAtom); const [txList] = useAtom(txListAtom);
const setMyGroupsWhereIAmAdmin = useSetAtom(myGroupsWhereIAmAdminAtom); const setMyGroupsWhereIAmAdmin = useSetAtom(myGroupsWhereIAmAdminAtom);
@ -91,7 +89,7 @@ export const GroupJoinRequests = ({
} }
}; };
React.useEffect(() => { useEffect(() => {
if (myAddress && groups.length > 0) { if (myAddress && groups.length > 0) {
getJoinRequests(); getJoinRequests();
} else { } else {
@ -99,7 +97,7 @@ export const GroupJoinRequests = ({
} }
}, [myAddress, groups]); }, [myAddress, groups]);
const filteredJoinRequests = React.useMemo(() => { const filteredJoinRequests = useMemo(() => {
return groupsWithJoinRequests.map((group) => { return groupsWithJoinRequests.map((group) => {
const filteredGroupRequests = group?.data?.filter((gd) => { const filteredGroupRequests = group?.data?.filter((gd) => {
const findJoinRequsetInTxList = txList?.find( const findJoinRequsetInTxList = txList?.find(
@ -149,6 +147,7 @@ export const GroupJoinRequests = ({
?.length > 0 && ?.length > 0 &&
` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`} ` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`}
</Typography> </Typography>
{isExpanded ? ( {isExpanded ? (
<ExpandLessIcon <ExpandLessIcon
sx={{ sx={{
@ -163,6 +162,7 @@ export const GroupJoinRequests = ({
/> />
)} )}
</ButtonBase> </ButtonBase>
<Collapse in={isExpanded} timeout="auto" unmountOnExit> <Collapse in={isExpanded} timeout="auto" unmountOnExit>
<Box <Box
sx={{ sx={{
@ -186,6 +186,7 @@ export const GroupJoinRequests = ({
<CustomLoader /> <CustomLoader />
</Box> </Box>
)} )}
{!loading && {!loading &&
(filteredJoinRequests.length === 0 || (filteredJoinRequests.length === 0 ||
filteredJoinRequests?.filter((group) => group?.data?.length > 0) filteredJoinRequests?.filter((group) => group?.data?.length > 0)
@ -212,6 +213,7 @@ export const GroupJoinRequests = ({
</Typography> </Typography>
</Box> </Box>
)} )}
<List <List
className="scrollable-container" className="scrollable-container"
sx={{ sx={{
@ -268,7 +270,14 @@ export const GroupJoinRequests = ({
fontWeight: 400, fontWeight: 400,
}, },
}} }}
primary={`${group?.group?.groupName} has ${group?.data?.length} pending join requests.`} primary={t(
'group:message.generic.pending_join_requests',
{
group: group?.group?.groupName,
count: group?.data?.length,
postProcess: 'capitalizeFirstChar',
}
)}
/> />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>

View File

@ -29,7 +29,6 @@ import {
isRunningPublicNodeAtom, isRunningPublicNodeAtom,
timestampEnterDataSelector, timestampEnterDataSelector,
} from '../../atoms/global'; } from '../../atoms/global';
import { timeDifferenceForNotificationChats } from './Group'; import { timeDifferenceForNotificationChats } from './Group';
import { useAtom, useAtomValue } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -56,23 +55,23 @@ export const GroupList = ({
return ( return (
<div <div
style={{ style={{
display: 'flex',
width: '380px',
flexDirection: 'column',
alignItems: 'flex-start', alignItems: 'flex-start',
height: '100%',
background: theme.palette.background.surface, background: theme.palette.background.surface,
borderRadius: '0px 15px 15px 0px', borderRadius: '0px 15px 15px 0px',
display: 'flex',
flexDirection: 'column',
height: '100%',
padding: '0px 2px', padding: '0px 2px',
width: '380px',
}} }}
> >
<Box <Box
sx={{ sx={{
width: '100%',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center',
display: 'flex', display: 'flex',
gap: '10px', gap: '10px',
justifyContent: 'center',
width: '100%',
}} }}
> >
<ButtonBase <ButtonBase
@ -252,7 +251,7 @@ const GroupItem = React.memo(
padding: '10px', padding: '10px',
width: '100%', width: '100%',
'&:hover': { '&:hover': {
backgroundColor: 'action.hover', // background on hover backgroundColor: 'action.hover',
}, },
}} }}
> >
@ -296,7 +295,7 @@ const GroupItem = React.memo(
theme.palette.text.primary, theme.palette.text.primary,
fontSize: '16px', fontSize: '16px',
}, },
}} // Change the color of the primary text }}
secondaryTypographyProps={{ secondaryTypographyProps={{
style: { style: {
color: color:

View File

@ -1,5 +1,5 @@
import { Box, Divider, Typography, useTheme } from '@mui/material'; import { Box, Divider, Typography, useTheme } from '@mui/material';
import React from 'react'; import { useEffect, useMemo, useState } from 'react';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { ThingsToDoInitial } from './ThingsToDoInitial'; import { ThingsToDoInitial } from './ThingsToDoInitial';
import { GroupJoinRequests } from './GroupJoinRequests'; import { GroupJoinRequests } from './GroupJoinRequests';
@ -28,28 +28,28 @@ export const HomeDesktop = ({
setDesktopViewMode, setDesktopViewMode,
desktopViewMode, desktopViewMode,
}) => { }) => {
const [checked1, setChecked1] = React.useState(false); const [checked1, setChecked1] = useState(false);
const [checked2, setChecked2] = React.useState(false); const [checked2, setChecked2] = useState(false);
const { t } = useTranslation(['auth', 'core', 'group']); const { t } = useTranslation(['auth', 'core', 'group']);
const theme = useTheme(); const theme = useTheme();
React.useEffect(() => { useEffect(() => {
if (balance && +balance >= 6) { if (balance && +balance >= 6) {
setChecked1(true); setChecked1(true);
} }
}, [balance]); }, [balance]);
React.useEffect(() => { useEffect(() => {
if (name) setChecked2(true); if (name) setChecked2(true);
}, [name]); }, [name]);
const isLoaded = React.useMemo(() => { const isLoaded = useMemo(() => {
if (userInfo !== null) return true; if (userInfo !== null) return true;
return false; return false;
}, [userInfo]); }, [userInfo]);
const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => { const hasDoneNameAndBalanceAndIsLoaded = useMemo(() => {
if (isLoaded && checked1 && checked2) return true; if (isLoaded && checked1 && checked2) return true;
return false; return false;
}, [checked1, isLoaded, checked2]); }, [checked1, isLoaded, checked2]);
@ -136,14 +136,6 @@ export const HomeDesktop = ({
{desktopViewMode === 'home' && ( {desktopViewMode === 'home' && (
<> <>
{/* <Box sx={{
width: '330px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<ListOfThreadPostsWatched />
</Box> */}
{hasDoneNameAndBalanceAndIsLoaded && ( {hasDoneNameAndBalanceAndIsLoaded && (
<> <>
<Box <Box

View File

@ -87,6 +87,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
const handleCancelBan = async (address) => { const handleCancelBan = async (address) => {
try { try {
const fee = await getFee('CANCEL_GROUP_BAN'); const fee = await getFee('CANCEL_GROUP_BAN');
await show({ await show({
message: t('core:message.question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'CANCEL_GROUP_BAN', action: 'CANCEL_GROUP_BAN',
@ -94,6 +95,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingUnban(true); setIsLoadingUnban(true);
new Promise((res, rej) => { new Promise((res, rej) => {
window window
@ -125,7 +127,11 @@ export const ListOfBans = ({ 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: 'capitalizeFirstChar',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);

View File

@ -1,10 +1,4 @@
import React, { import { useCallback, useContext, useEffect, useRef, useState } from 'react';
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { import {
Avatar, Avatar,
Box, Box,
@ -88,7 +82,7 @@ export const ListOfGroupPromotions = () => {
const [promotionTimeInterval, setPromotionTimeInterval] = useAtom( const [promotionTimeInterval, setPromotionTimeInterval] = useAtom(
promotionTimeIntervalAtom promotionTimeIntervalAtom
); );
const [isExpanded, setIsExpanded] = React.useState(false); const [isExpanded, setIsExpanded] = 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);
@ -101,7 +95,7 @@ export const ListOfGroupPromotions = () => {
const listRef = useRef(null); const listRef = useRef(null);
const rowVirtualizer = useVirtualizer({ const rowVirtualizer = useVirtualizer({
count: promotions.length, count: promotions.length,
getItemKey: React.useCallback( getItemKey: useCallback(
(index) => promotions[index]?.identifier, (index) => promotions[index]?.identifier,
[promotions] [promotions]
), ),
@ -275,6 +269,7 @@ export const ListOfGroupPromotions = () => {
try { try {
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('core:message.question.perform_transaction', { message: t('core:message.question.perform_transaction', {
action: 'JOIN_GROUP', action: 'JOIN_GROUP',
@ -282,6 +277,7 @@ export const ListOfGroupPromotions = () => {
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingJoinGroup(true); setIsLoadingJoinGroup(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
@ -495,6 +491,7 @@ export const ListOfGroupPromotions = () => {
<CustomLoader /> <CustomLoader />
</Box> </Box>
)} )}
{!loading && promotions.length === 0 && ( {!loading && promotions.length === 0 && (
<Box <Box
sx={{ sx={{
@ -518,6 +515,7 @@ export const ListOfGroupPromotions = () => {
</Typography> </Typography>
</Box> </Box>
)} )}
<div <div
style={{ style={{
height: '600px', height: '600px',
@ -776,6 +774,7 @@ export const ListOfGroupPromotions = () => {
}} }}
/> />
)} )}
{promotion?.isOpen === true && ( {promotion?.isOpen === true && (
<NoEncryptionGmailerrorredIcon <NoEncryptionGmailerrorredIcon
sx={{ sx={{
@ -783,6 +782,7 @@ export const ListOfGroupPromotions = () => {
}} }}
/> />
)} )}
<Typography <Typography
sx={{ sx={{
fontSize: '15px', fontSize: '15px',

View File

@ -210,13 +210,13 @@ export const ListOfJoinRequests = ({
> >
<Box <Box
sx={{ sx={{
width: '325px', alignItems: 'center',
height: '250px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
gap: '10px', gap: '10px',
height: '250px',
padding: '10px', padding: '10px',
width: '325px',
}} }}
> >
<LoadingButton <LoadingButton
@ -261,12 +261,12 @@ export const ListOfJoinRequests = ({
</p> </p>
<div <div
style={{ style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexShrink: 1, flexShrink: 1,
height: '500px',
position: 'relative',
width: '100%',
}} }}
> >
<AutoSizer> <AutoSizer>

View File

@ -117,6 +117,7 @@ export const ListOfThreadPostsWatched = () => {
<CustomLoader /> <CustomLoader />
</Box> </Box>
)} )}
{!loading && posts.length === 0 && ( {!loading && posts.length === 0 && (
<Box <Box
sx={{ sx={{
@ -140,6 +141,7 @@ export const ListOfThreadPostsWatched = () => {
</Typography> </Typography>
</Box> </Box>
)} )}
{posts?.length > 0 && ( {posts?.length > 0 && (
<List <List
className="scrollable-container" className="scrollable-container"
@ -173,7 +175,10 @@ export const ListOfThreadPostsWatched = () => {
> >
<ListItemButton disableRipple role={undefined} dense> <ListItemButton disableRipple role={undefined} dense>
<ListItemText <ListItemText
primary={`New post in ${post?.thread?.threadData?.title}`} primary={t('core:new_post_in', {
title: post?.thread?.threadData?.title,
postProcess: 'capitalizeFirstChar',
})}
/> />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>

View File

@ -367,9 +367,9 @@ export const ManageMembers = ({
{value === 0 && ( {value === 0 && (
<Box <Box
sx={{ sx={{
width: '100%',
padding: '25px',
maxWidth: '750px', maxWidth: '750px',
padding: '25px',
width: '100%',
}} }}
> >
<Button <Button
@ -398,9 +398,9 @@ export const ManageMembers = ({
{value === 1 && ( {value === 1 && (
<Box <Box
sx={{ sx={{
width: '100%',
padding: '25px',
maxWidth: '750px', maxWidth: '750px',
padding: '25px',
width: '100%',
}} }}
> >
<InviteMember <InviteMember
@ -415,9 +415,9 @@ export const ManageMembers = ({
{value === 2 && ( {value === 2 && (
<Box <Box
sx={{ sx={{
width: '100%',
padding: '25px',
maxWidth: '750px', maxWidth: '750px',
padding: '25px',
width: '100%',
}} }}
> >
<ListOfInvites <ListOfInvites
@ -432,8 +432,8 @@ export const ManageMembers = ({
{value === 3 && ( {value === 3 && (
<Box <Box
sx={{ sx={{
width: '100%',
padding: '25px', padding: '25px',
width: '100%',
maxWidth: '750px', maxWidth: '750px',
}} }}
> >
@ -449,9 +449,9 @@ export const ManageMembers = ({
{value === 4 && ( {value === 4 && (
<Box <Box
sx={{ sx={{
width: '100%',
padding: '25px',
maxWidth: '750px', maxWidth: '750px',
padding: '25px',
width: '100%',
}} }}
> >
<ListOfJoinRequests <ListOfJoinRequests

View File

@ -210,6 +210,7 @@ export const QMailMessages = ({ userName, userAddress }) => {
<CustomLoader /> <CustomLoader />
</Box> </Box>
)} )}
{!loading && mails.length === 0 && ( {!loading && mails.length === 0 && (
<Box <Box
sx={{ sx={{

View File

@ -199,6 +199,7 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
postProcess: 'capitalizeFirstChar', postProcess: 'capitalizeFirstChar',
})} })}
/> />
{window?.electronAPI && ( {window?.electronAPI && (
<FormControlLabel <FormControlLabel
control={ control={
@ -320,6 +321,7 @@ const ExportPrivateKey = ({ rawWallet }) => {
autoComplete="off" autoComplete="off"
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
/> />
{privateKey && ( {privateKey && (
<Button <Button
variant="outlined" variant="outlined"

View File

@ -12,8 +12,10 @@ import { AppsNavBarLeft, AppsNavBarParent } from '../Apps/Apps-styles';
import { NavBack } from '../../assets/Icons/NavBack.tsx'; import { NavBack } from '../../assets/Icons/NavBack.tsx';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
export const WalletsAppWrapper = () => { export const WalletsAppWrapper = () => {
const { t } = useTranslation(['auth', 'core', 'group']);
const iframeRef = useRef(null); const iframeRef = useRef(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [navigationController, setNavigationController] = useAtom( const [navigationController, setNavigationController] = useAtom(
@ -87,7 +89,11 @@ export const WalletsAppWrapper = () => {
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
<Typography>Q-Wallets</Typography> <Typography>
{t('core:q_apps.q_wallets', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
<ButtonBase onClick={handleClose}> <ButtonBase onClick={handleClose}>
<CloseIcon <CloseIcon

View File

@ -16,6 +16,7 @@ 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 MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
const [hasAvatar, setHasAvatar] = useState(false); const [hasAvatar, setHasAvatar] = useState(false);
@ -40,6 +41,8 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const id = open ? 'avatar-img' : undefined; const id = open ? 'avatar-img' : undefined;
const { t } = useTranslation(['auth', 'core', 'group']);
const checkIfAvatarExists = async () => { const checkIfAvatarExists = async () => {
try { try {
const identifier = `qortal_avatar`; const identifier = `qortal_avatar`;
@ -65,16 +68,26 @@ 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(
t('core:message.generic.avatar_publish_fee', {
fee: fee.fee,
postProcess: 'capitalizeFirstChar',
})
);
await show({ await show({
message: 'Would you like to publish an avatar?', message: t('core:message.question.publish_avatar', {
postProcess: 'capitalizeFirstChar',
}),
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', {
@ -90,7 +103,12 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
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: 'capitalizeFirstChar',
})
);
}); });
}); });
setAvatarFile(null); setAvatarFile(null);
@ -122,6 +140,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
> >
{myName?.charAt(0)} {myName?.charAt(0)}
</Avatar> </Avatar>
<ButtonBase onClick={handleChildClick}> <ButtonBase onClick={handleChildClick}>
<Typography <Typography
sx={{ sx={{
@ -129,9 +148,12 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
opacity: 0.5, opacity: 0.5,
}} }}
> >
change avatar {t('core:action.change_avatar', {
postProcess: 'capitalizeFirstChar',
})}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
<PopoverComp <PopoverComp
myName={myName} myName={myName}
avatarFile={avatarFile} avatarFile={avatarFile}
@ -160,6 +182,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
> >
{myName?.charAt(0)} {myName?.charAt(0)}
</Avatar> </Avatar>
<ButtonBase onClick={handleChildClick}> <ButtonBase onClick={handleChildClick}>
<Typography <Typography
sx={{ sx={{
@ -167,9 +190,12 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
opacity: 0.5, opacity: 0.5,
}} }}
> >
change avatar {t('core:action.change_avatar', {
postProcess: 'capitalizeFirstChar',
})}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
<PopoverComp <PopoverComp
myName={myName} myName={myName}
avatarFile={avatarFile} avatarFile={avatarFile}
@ -195,9 +221,10 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
opacity: 0.5, opacity: 0.5,
}} }}
> >
set avatar {t('core:action.set_avatar', { postProcess: 'capitalizeFirstChar' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
<PopoverComp <PopoverComp
myName={myName} myName={myName}
avatarFile={avatarFile} avatarFile={avatarFile}
@ -213,6 +240,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => {
); );
}; };
// TODO the following part is the same as in GroupAvatar.tsx
const PopoverComp = ({ const PopoverComp = ({
avatarFile, avatarFile,
setAvatarFile, setAvatarFile,
@ -225,6 +253,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}
@ -246,13 +276,24 @@ const PopoverComp = ({
fontSize: '12px', fontSize: '12px',
}} }}
> >
(500 KB max. for GIFS){' '} {t('core:message.generic.avatar_size', {
size: 500, // TODO magic number
postProcess: 'capitalizeFirstChar',
})}
</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: 'capitalizeFirstChar',
})}
</Button>
</ImageUploader> </ImageUploader>
{avatarFile?.name} {avatarFile?.name}
<Spacer height="25px" /> <Spacer height="25px" />
{!myName && ( {!myName && (
<Box <Box
sx={{ sx={{
@ -267,19 +308,24 @@ const PopoverComp = ({
}} }}
/> />
<Typography> <Typography>
A registered name is required to set an avatar {t('core:message.generic.avatar_registered_name', {
postProcess: 'capitalizeFirstChar',
})}
</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: 'capitalizeFirstChar',
})}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>

View File

@ -1,19 +1,13 @@
import { Box, CircularProgress, useTheme } from '@mui/material'; import { Box, useTheme } from '@mui/material';
import { useState } from 'react'; import { useState } from 'react';
import { import { TextP } from '../styles/App-styles';
CustomButton,
CustomInput,
CustomLabel,
TextP,
} from '../styles/App-styles';
import { Spacer } from '../common/Spacer'; import { Spacer } from '../common/Spacer';
import BoundedNumericTextField from '../common/BoundedNumericTextField';
import { PasswordField } from './PasswordField/PasswordField';
import { ErrorText } from './ErrorText/ErrorText';
import { getFee } from '../background'; import { getFee } from '../background';
import { useTranslation } from 'react-i18next';
export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const [paymentTo, setPaymentTo] = useState<string>(defaultPaymentTo); const [paymentTo, setPaymentTo] = useState<string>(defaultPaymentTo);
const [paymentAmount, setPaymentAmount] = useState<number>(0); const [paymentAmount, setPaymentAmount] = useState<number>(0);
const [paymentPassword, setPaymentPassword] = useState<string>(''); const [paymentPassword, setPaymentPassword] = useState<string>('');
@ -26,22 +20,37 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
setSendPaymentError(''); setSendPaymentError('');
setSendPaymentSuccess(''); setSendPaymentSuccess('');
if (!paymentTo) { if (!paymentTo) {
setSendPaymentError('Please enter a recipient'); setSendPaymentError(
t('auth:action.enter_recipient', {
postProcess: 'capitalizeFirstChar',
})
);
return; return;
} }
if (!paymentAmount) { if (!paymentAmount) {
setSendPaymentError('Please enter an amount greater than 0'); setSendPaymentError(
t('auth:action.enter_amount', {
postProcess: 'capitalizeFirstChar',
})
);
return; return;
} }
if (!paymentPassword) { if (!paymentPassword) {
setSendPaymentError('Please enter your wallet password'); setSendPaymentError(
t('auth:action.enter_wallet_password', {
postProcess: 'capitalizeFirstChar',
})
);
return; return;
} }
const fee = await getFee('PAYMENT'); // TODO translate const fee = await getFee('PAYMENT');
await show({ await show({
message: `Would you like to transfer ${Number(paymentAmount)} QORT?`, message: t('core:message.question.transfer_qort', {
amount: Number(paymentAmount),
postProcess: 'capitalizeFirstChar',
}),
paymentFee: fee.fee + ' QORT', paymentFee: fee.fee + ' QORT',
}); });
@ -87,7 +96,9 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
textAlign: 'start', textAlign: 'start',
}} }}
> >
Transfer QORT {t('core:action.transfer_qort', {
postProcess: 'capitalizeFirstChar',
})}
</TextP> </TextP>
<Spacer height="35px" /> <Spacer height="35px" />
@ -101,7 +112,9 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
textAlign: 'start', textAlign: 'start',
}} }}
> >
Balance: {t('core:balance', {
postProcess: 'capitalizeFirstChar',
})}
</TextP> </TextP>
<TextP <TextP
@ -119,7 +132,11 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
<Spacer height="35px" /> <Spacer height="35px" />
<Box> <Box>
<CustomLabel htmlFor="standard-adornment-name">To</CustomLabel> <CustomLabel htmlFor="standard-adornment-name">
{t('core:to', {
postProcess: 'capitalizeFirstChar',
})}
</CustomLabel>
<Spacer height="5px" /> <Spacer height="5px" />
@ -132,7 +149,11 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
<Spacer height="6px" /> <Spacer height="6px" />
<CustomLabel htmlFor="standard-adornment-amount">Amount</CustomLabel> <CustomLabel htmlFor="standard-adornment-amount">
{t('core:amount', {
postProcess: 'capitalizeFirstChar',
})}
</CustomLabel>
<Spacer height="5px" /> <Spacer height="5px" />
@ -149,7 +170,9 @@ 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 {t('auth:wallet.password_confirmation', {
postProcess: 'capitalizeFirstChar',
})}
</CustomLabel> </CustomLabel>
<Spacer height="5px" /> <Spacer height="5px" />
@ -171,7 +194,6 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
<Spacer height="10px" /> <Spacer height="10px" />
<ErrorText>{sendPaymentError}</ErrorText> <ErrorText>{sendPaymentError}</ErrorText>
{/* <Typography>{sendPaymentSuccess}</Typography> */}
<Spacer height="25px" /> <Spacer height="25px" />
@ -192,7 +214,7 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
}} }}
/> />
)} )}
Send {t('core:action.send', { postProcess: 'capitalizeFirstChar' })}
</CustomButton> </CustomButton>
</> </>
); );

View File

@ -33,6 +33,7 @@ import {
unsubscribeFromEvent, unsubscribeFromEvent,
} from '../../utils/events'; } from '../../utils/events';
import { useNameSearch } from '../../hooks/useNameSearch'; import { useNameSearch } from '../../hooks/useNameSearch';
import { useTranslation } from 'react-i18next';
function formatAddress(str) { function formatAddress(str) {
if (str.length <= 12) return str; if (str.length <= 12) return str;
@ -45,6 +46,7 @@ function formatAddress(str) {
export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const [nameOrAddress, setNameOrAddress] = useState(''); const [nameOrAddress, setNameOrAddress] = useState('');
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const { results, isLoading } = useNameSearch(inputValue); const { results, isLoading } = useNameSearch(inputValue);
@ -64,13 +66,27 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
const inputAddressOrName = messageAddressOrName || nameOrAddress; const inputAddressOrName = messageAddressOrName || nameOrAddress;
if (!inputAddressOrName?.trim()) if (!inputAddressOrName?.trim())
throw new Error('Please insert a name or address'); throw new Error(
t('auth:action.insert_name_address', {
postProcess: 'capitalizeFirstChar',
})
);
const owner = await getNameOrAddress(inputAddressOrName); const owner = await getNameOrAddress(inputAddressOrName);
if (!owner) throw new Error('Name does not exist'); if (!owner)
throw new Error(
t('auth:message.error.name_not_existing', {
postProcess: 'capitalizeFirstChar',
})
);
const addressInfoRes = await getAddressInfo(owner); const addressInfoRes = await getAddressInfo(owner);
if (!addressInfoRes?.publicKey) { if (!addressInfoRes?.publicKey) {
throw new Error('Address does not exist on blockchain'); throw new Error(
t('auth:message.error.address_not_existing', {
postProcess: 'capitalizeFirstChar',
})
);
} }
const name = await getNameInfo(owner); const name = await getNameInfo(owner);
@ -175,7 +191,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
autoFocus autoFocus
autoComplete="off" autoComplete="off"
{...params} {...params}
label="Address or Name" label={t('auth:address_name', {
postProcess: 'capitalizeFirstChar',
})}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter' && nameOrAddress) { if (e.key === 'Enter' && nameOrAddress) {
lookupFunc(inputValue); lookupFunc(inputValue);
@ -200,6 +218,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
/> />
</ButtonBase> </ButtonBase>
</Box> </Box>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -220,6 +239,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
<Typography>{errorMessage}</Typography> <Typography>{errorMessage}</Typography>
</Box> </Box>
)} )}
{isLoadingUser && ( {isLoadingUser && (
<Box <Box
sx={{ sx={{
@ -236,6 +256,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
/> />
</Box> </Box>
)} )}
{!isLoadingUser && addressInfo && ( {!isLoadingUser && addressInfo && (
<> <>
<Spacer height="30px" /> <Spacer height="30px" />
@ -265,7 +286,10 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
textAlign: 'center', textAlign: 'center',
}} }}
> >
{addressInfo?.name ?? 'Name not registered'} {addressInfo?.name ??
t('auth:message.error.name_not_registered', {
postProcess: 'capitalizeFirstChar',
})}
</Typography> </Typography>
<Spacer height="20px" /> <Spacer height="20px" />
@ -307,7 +331,8 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
textAlign: 'center', textAlign: 'center',
}} }}
> >
Level {addressInfo?.level} {t('core:level', { postProcess: 'capitalizeFirstChar' })}{' '}
{addressInfo?.level}
</Typography> </Typography>
</Card> </Card>
@ -336,8 +361,13 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
flexShrink: 0, flexShrink: 0,
}} }}
> >
<Typography>Address</Typography> <Typography>
{t('auth:address', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</Box> </Box>
<Tooltip <Tooltip
title={ title={
<span <span
@ -347,7 +377,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
fontWeight: 700, fontWeight: 700,
}} }}
> >
copy address {t('auth:action.copy_address', {
postProcess: 'capitalizeFirstChar',
})}
</span> </span>
} }
placement="bottom" placement="bottom"
@ -391,7 +423,12 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
width: '100%', width: '100%',
}} }}
> >
<Typography>Balance</Typography> <Typography>
{t('core:balance', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
<Typography>{addressInfo?.balance}</Typography> <Typography>{addressInfo?.balance}</Typography>
</Box> </Box>
@ -406,7 +443,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
}); });
}} }}
> >
Send QORT {t('core:action.send_qort', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
</Card> </Card>
</Box> </Box>
@ -440,7 +479,12 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
padding: '15px', padding: '15px',
}} }}
> >
<Typography>20 most recent payments</Typography> <Typography>
{t('core:message.generic.most_recent_payment', {
count: 20,
postProcess: 'capitalizeFirstChar',
})}
</Typography>
<Spacer height="20px" /> <Spacer height="20px" />
@ -452,17 +496,33 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
width: '100%', width: '100%',
}} }}
> >
<Typography>No payments</Typography> <Typography>
{t('core:message.generic.no_payments', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</Box> </Box>
)} )}
<Table> <Table>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell>Sender</TableCell> <TableCell>
<TableCell>Reciver</TableCell> {t('core:sender', { postProcess: 'capitalizeFirstChar' })}
<TableCell>Amount</TableCell> </TableCell>
<TableCell>Time</TableCell> <TableCell>
{t('core:receiver', {
postProcess: 'capitalizeFirstChar',
})}
</TableCell>
<TableCell>
{t('core:amount', { postProcess: 'capitalizeFirstChar' })}
</TableCell>
<TableCell>
{t('core:time.time', {
postProcess: 'capitalizeFirstChar',
})}
</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
@ -479,7 +539,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
fontWeight: 700, fontWeight: 700,
}} }}
> >
copy address {t('auth:action.copy_address', {
postProcess: 'capitalizeFirstChar',
})}
</span> </span>
} }
placement="bottom" placement="bottom"
@ -522,7 +584,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
fontWeight: 700, fontWeight: 700,
}} }}
> >
copy address {t('auth:action.copy_address', {
postProcess: 'capitalizeFirstChar',
})}
</span> </span>
} }
placement="bottom" placement="bottom"
@ -552,7 +616,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
</ButtonBase> </ButtonBase>
</Tooltip> </Tooltip>
</TableCell> </TableCell>
<TableCell>{payment?.amount}</TableCell> <TableCell>{payment?.amount}</TableCell>
<TableCell> <TableCell>
{formatTimestamp(payment?.timestamp)} {formatTimestamp(payment?.timestamp)}
</TableCell> </TableCell>

View File

@ -10,9 +10,11 @@ import { executeEvent } from '../utils/events';
import { MyContext } from '../App'; import { MyContext } from '../App';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { isRunningPublicNodeAtom } from '../atoms/global'; import { isRunningPublicNodeAtom } from '../atoms/global';
import { useTranslation } from 'react-i18next';
export const WrapperUserAction = ({ children, address, name, disabled }) => { export const WrapperUserAction = ({ children, address, name, disabled }) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom); const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom);
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
@ -96,7 +98,7 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
justifyContent: 'flex-start', justifyContent: 'flex-start',
}} }}
> >
Message {t('core:message.message', { postProcess: 'capitalizeFirstChar' })}
</Button> </Button>
{/* Option 2: Send QORT */} {/* Option 2: Send QORT */}
@ -114,8 +116,11 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
justifyContent: 'flex-start', justifyContent: 'flex-start',
}} }}
> >
Send QORT {t('core:action.send_qort', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
<Button <Button
variant="text" variant="text"
onClick={() => { onClick={() => {
@ -127,8 +132,11 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
justifyContent: 'flex-start', justifyContent: 'flex-start',
}} }}
> >
Copy address {t('auth:action.copy_address', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
<Button <Button
variant="text" variant="text"
onClick={() => { onClick={() => {
@ -142,7 +150,9 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
justifyContent: 'flex-start', justifyContent: 'flex-start',
}} }}
> >
User lookup {t('core:user_lookup', {
postProcess: 'capitalizeFirstChar',
})}
</Button> </Button>
{!isRunningPublicNode && ( {!isRunningPublicNode && (
@ -165,6 +175,7 @@ const BlockUser = ({ address, name, handleClose }) => {
const { isUserBlocked, addToBlockList, removeBlockFromList } = const { isUserBlocked, addToBlockList, removeBlockFromList } =
useContext(MyContext); useContext(MyContext);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['auth', 'core', 'group']);
useEffect(() => { useEffect(() => {
if (!address) return; if (!address) return;
@ -180,12 +191,6 @@ const BlockUser = ({ address, name, handleClose }) => {
executeEvent('blockUserFromOutside', { executeEvent('blockUserFromOutside', {
user: address, user: address,
}); });
// if(isAlreadyBlocked === true){
// await removeBlockFromList(address, name)
// } else if(isAlreadyBlocked === false) {
// await addToBlockList(address, name)
// }
// executeEvent('updateChatMessagesWithBlocks', true)
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally { } finally {
@ -202,8 +207,9 @@ const BlockUser = ({ address, name, handleClose }) => {
{(isAlreadyBlocked === null || isLoading) && ( {(isAlreadyBlocked === null || isLoading) && (
<CircularProgress color="secondary" size={24} /> <CircularProgress color="secondary" size={24} />
)} )}
{isAlreadyBlocked && 'Unblock name'} {isAlreadyBlocked &&
{isAlreadyBlocked === false && 'Block name'} t('auth:action.unblock_name', { postProcess: 'capitalizeFirstChar' })}
{isAlreadyBlocked === false && t('auth:action.block_name', { postProcess: 'capitalizeFirstChar' })}}
</Button> </Button>
); );
}; };

View File

@ -10,6 +10,7 @@ import { saveToLocalStorage } from '../components/Apps/AppsNavBarDesktop';
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 { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
export const useHandlePrivateApps = () => { export const useHandlePrivateApps = () => {
const [status, setStatus] = useState(''); const [status, setStatus] = useState('');
@ -20,8 +21,8 @@ export const useHandlePrivateApps = () => {
setInfoSnackCustom, setInfoSnackCustom,
} = useContext(MyContext); } = useContext(MyContext);
const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom); const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom);
const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom); const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
const { t } = useTranslation(['auth', 'core', 'group']);
const openApp = async ( const openApp = async (
privateAppProperties, privateAppProperties,
@ -30,13 +31,19 @@ export const useHandlePrivateApps = () => {
) => { ) => {
try { try {
if (setLoadingStatePrivateApp) { if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp(`Downloading and decrypting private app.`); setLoadingStatePrivateApp(
t('core:message.generic.downloading_decrypting_app', {
postProcess: 'capitalizeFirstChar',
})
);
} }
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'info', type: 'info',
message: 'Fetching app data', message: t('core:message.generic.fetching_data', {
postProcess: 'capitalizeFirstChar',
}),
duration: null, duration: null,
}); });
const urlData = `${getBaseApiReact()}/arbitrary/${ const urlData = `${getBaseApiReact()}/arbitrary/${
@ -55,7 +62,11 @@ export const useHandlePrivateApps = () => {
if (!responseData?.ok) { if (!responseData?.ok) {
if (setLoadingStatePrivateApp) { if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to download private app.'); setLoadingStatePrivateApp(
t('core:message.generic.unable_download_private_app', {
postProcess: 'capitalizeFirstChar',
})
);
} }
throw new Error('Unable to fetch app'); throw new Error('Unable to fetch app');
@ -64,13 +75,25 @@ export const useHandlePrivateApps = () => {
data = await responseData.text(); data = await responseData.text();
if (data?.error) { if (data?.error) {
if (setLoadingStatePrivateApp) { if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to download private app.'); setLoadingStatePrivateApp(
t('core:message.generic.unable_download_private_app', {
postProcess: 'capitalizeFirstChar',
})
);
} }
throw new Error('Unable to fetch app'); throw new Error(
t('core:message.generic.unable_fetch_app', {
postProcess: 'capitalizeFirstChar',
})
);
} }
} catch (error) { } catch (error) {
if (setLoadingStatePrivateApp) { if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to download private app.'); setLoadingStatePrivateApp(
t('core:message.generic.unable_download_private_app', {
postProcess: 'capitalizeFirstChar',
})
);
} }
throw error; throw error;
} }
@ -78,23 +101,27 @@ export const useHandlePrivateApps = () => {
let decryptedData; let decryptedData;
// eslint-disable-next-line no-useless-catch // eslint-disable-next-line no-useless-catch
try { try {
decryptedData = await window.sendMessage( decryptedData = await window.sendMessage('DECRYPT_QORTAL_GROUP_DATA', {
'DECRYPT_QORTAL_GROUP_DATA',
{
base64: data, base64: data,
groupId: privateAppProperties?.groupId, groupId: privateAppProperties?.groupId,
} });
);
if (decryptedData?.error) { if (decryptedData?.error) {
if (setLoadingStatePrivateApp) { if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to decrypt private app.'); setLoadingStatePrivateApp(
t('core:message.generic.unable_decrypt_app', {
postProcess: 'capitalizeFirstChar',
})
);
} }
throw new Error(decryptedData?.error); throw new Error(decryptedData?.error);
} }
} catch (error) { } catch (error) {
if (setLoadingStatePrivateApp) { if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to decrypt private app.'); setLoadingStatePrivateApp(
t('core:message.generic.unable_decrypt_app', {
postProcess: 'capitalizeFirstChar',
})
);
} }
throw error; throw error;
} }
@ -106,11 +133,15 @@ export const useHandlePrivateApps = () => {
if (decryptedData) { if (decryptedData) {
setInfoSnackCustom({ setInfoSnackCustom({
type: 'info', type: 'info',
message: 'Building app', message: t('core:message.generic.building_app', {
postProcess: 'capitalizeFirstChar',
}),
}); });
const endpoint = await createEndpoint( const endpoint = await createEndpoint(
`/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true` `/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true`
); );
const response = await fetch(endpoint, { const response = await fetch(endpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -118,7 +149,9 @@ export const useHandlePrivateApps = () => {
}, },
body: UintToObject?.app, body: UintToObject?.app,
}); });
const previewPath = await response.text(); const previewPath = await response.text();
const refreshfunc = async (tabId, privateAppProperties) => { const refreshfunc = async (tabId, privateAppProperties) => {
const checkIfPreviewLinkStillWorksUrl = await createEndpoint( const checkIfPreviewLinkStillWorksUrl = await createEndpoint(
`/render/hash/HmtnZpcRPwisMfprUXuBp27N2xtv5cDiQjqGZo8tbZS?secret=E39WTiG4qBq3MFcMPeRZabtQuzyfHg9ZuR5SgY7nW1YH` `/render/hash/HmtnZpcRPwisMfprUXuBp27N2xtv5cDiQjqGZo8tbZS?secret=E39WTiG4qBq3MFcMPeRZabtQuzyfHg9ZuR5SgY7nW1YH`
@ -132,6 +165,7 @@ export const useHandlePrivateApps = () => {
const endpoint = await createEndpoint( const endpoint = await createEndpoint(
`/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true` `/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true`
); );
const response = await fetch(endpoint, { const response = await fetch(endpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -139,6 +173,7 @@ export const useHandlePrivateApps = () => {
}, },
body: UintToObject?.app, body: UintToObject?.app,
}); });
const previewPath = await response.text(); const previewPath = await response.text();
executeEvent('updateAppUrl', { executeEvent('updateAppUrl', {
tabId: tabId, tabId: tabId,
@ -173,7 +208,9 @@ export const useHandlePrivateApps = () => {
}); });
setInfoSnackCustom({ setInfoSnackCustom({
type: 'success', type: 'success',
message: 'Opened', message: t('core:message.generic.opened', {
postProcess: 'capitalizeFirstChar',
}),
}); });
if (setLoadingStatePrivateApp) { if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp(``); setLoadingStatePrivateApp(``);
@ -206,7 +243,12 @@ export const useHandlePrivateApps = () => {
} catch (error) { } catch (error) {
if (setLoadingStatePrivateApp) { if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp( setLoadingStatePrivateApp(
`Error! ${error?.message || 'Unable to build private app.'}` `Error! ${
error?.message ||
t('core:message.error.unable_build_app', {
postProcess: 'capitalizeFirstChar',
})
}`
); );
} }
throw error; throw error;
@ -214,7 +256,11 @@ export const useHandlePrivateApps = () => {
} catch (error) { } catch (error) {
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: error?.message || 'Unable to fetch app', message:
error?.message ||
t('core:message.error.unable_fetch_app', {
postProcess: 'capitalizeFirstChar',
}),
}); });
} }
}; };

View File

@ -168,157 +168,157 @@ export function openIndexedDB() {
} }
export const listOfAllQortalRequests = [ export const listOfAllQortalRequests = [
'GET_USER_ACCOUNT',
'DECRYPT_DATA',
'SEND_COIN',
'GET_LIST_ITEMS',
'ADD_LIST_ITEMS',
'DELETE_LIST_ITEM',
'VOTE_ON_POLL',
'CREATE_POLL',
'SEND_CHAT_MESSAGE',
'JOIN_GROUP',
'DEPLOY_AT',
'GET_USER_WALLET',
'GET_WALLET_BALANCE',
'GET_USER_WALLET_INFO',
'GET_CROSSCHAIN_SERVER_INFO',
'GET_TX_ACTIVITY_SUMMARY',
'GET_FOREIGN_FEE',
'UPDATE_FOREIGN_FEE',
'GET_SERVER_CONNECTION_HISTORY',
'SET_CURRENT_FOREIGN_SERVER',
'ADD_FOREIGN_SERVER', 'ADD_FOREIGN_SERVER',
'REMOVE_FOREIGN_SERVER', 'ADD_GROUP_ADMIN',
'GET_DAY_SUMMARY', 'ADD_LIST_ITEMS',
'ADMIN_ACTION',
'BAN_FROM_GROUP',
'BUY_NAME',
'CANCEL_GROUP_BAN',
'CANCEL_GROUP_INVITE',
'CANCEL_SELL_NAME',
'CANCEL_TRADE_SELL_ORDER',
'CREATE_AND_COPY_EMBED_LINK',
'CREATE_GROUP',
'CREATE_POLL',
'CREATE_TRADE_BUY_ORDER', 'CREATE_TRADE_BUY_ORDER',
'CREATE_TRADE_SELL_ORDER', 'CREATE_TRADE_SELL_ORDER',
'CANCEL_TRADE_SELL_ORDER', 'DECRYPT_AESGCM',
'IS_USING_PUBLIC_NODE',
'ADMIN_ACTION',
'SIGN_TRANSACTION',
'OPEN_NEW_TAB',
'CREATE_AND_COPY_EMBED_LINK',
'DECRYPT_QORTAL_GROUP_DATA',
'DECRYPT_DATA_WITH_SHARING_KEY', 'DECRYPT_DATA_WITH_SHARING_KEY',
'DECRYPT_DATA',
'DECRYPT_QORTAL_GROUP_DATA',
'DELETE_HOSTED_DATA', 'DELETE_HOSTED_DATA',
'GET_HOSTED_DATA', 'DELETE_LIST_ITEM',
'PUBLISH_MULTIPLE_QDN_RESOURCES', 'DEPLOY_AT',
'PUBLISH_QDN_RESOURCE',
'ENCRYPT_DATA',
'ENCRYPT_DATA_WITH_SHARING_KEY', 'ENCRYPT_DATA_WITH_SHARING_KEY',
'ENCRYPT_DATA',
'ENCRYPT_QORTAL_GROUP_DATA', 'ENCRYPT_QORTAL_GROUP_DATA',
'SAVE_FILE', 'FETCH_BLOCK_RANGE',
'FETCH_BLOCK',
'FETCH_QDN_RESOURCE',
'GET_ACCOUNT_DATA', 'GET_ACCOUNT_DATA',
'GET_ACCOUNT_NAMES', 'GET_ACCOUNT_NAMES',
'SEARCH_NAMES', 'GET_ARRR_SYNC_STATUS',
'GET_NAME_DATA',
'GET_QDN_RESOURCE_URL',
'LINK_TO_QDN_RESOURCE',
'LIST_QDN_RESOURCES',
'SEARCH_QDN_RESOURCES',
'FETCH_QDN_RESOURCE',
'GET_QDN_RESOURCE_STATUS',
'GET_QDN_RESOURCE_PROPERTIES',
'GET_QDN_RESOURCE_METADATA',
'SEARCH_CHAT_MESSAGES',
'LIST_GROUPS',
'GET_BALANCE',
'GET_AT',
'GET_AT_DATA', 'GET_AT_DATA',
'LIST_ATS', 'GET_AT',
'FETCH_BLOCK', 'GET_BALANCE',
'FETCH_BLOCK_RANGE', 'GET_CROSSCHAIN_SERVER_INFO',
'SEARCH_TRANSACTIONS', 'GET_DAY_SUMMARY',
'GET_PRICE', 'GET_FOREIGN_FEE',
'SHOW_ACTIONS', 'GET_HOSTED_DATA',
'REGISTER_NAME', 'GET_LIST_ITEMS',
'UPDATE_NAME', 'GET_NAME_DATA',
'LEAVE_GROUP',
'INVITE_TO_GROUP',
'KICK_FROM_GROUP',
'BAN_FROM_GROUP',
'CANCEL_GROUP_BAN',
'ADD_GROUP_ADMIN',
'REMOVE_GROUP_ADMIN',
'DECRYPT_AESGCM',
'CANCEL_GROUP_INVITE',
'CREATE_GROUP',
'GET_USER_WALLET_TRANSACTIONS',
'GET_NODE_INFO', 'GET_NODE_INFO',
'GET_NODE_STATUS', 'GET_NODE_STATUS',
'GET_ARRR_SYNC_STATUS', 'GET_PRICE',
'SHOW_PDF_READER', 'GET_QDN_RESOURCE_METADATA',
'UPDATE_GROUP', 'GET_QDN_RESOURCE_PROPERTIES',
'SELL_NAME', 'GET_QDN_RESOURCE_STATUS',
'CANCEL_SELL_NAME', 'GET_QDN_RESOURCE_URL',
'BUY_NAME', 'GET_SERVER_CONNECTION_HISTORY',
'SIGN_FOREIGN_FEES', 'GET_TX_ACTIVITY_SUMMARY',
'GET_USER_ACCOUNT',
'GET_USER_WALLET_INFO',
'GET_USER_WALLET_TRANSACTIONS',
'GET_USER_WALLET',
'GET_WALLET_BALANCE',
'INVITE_TO_GROUP',
'IS_USING_PUBLIC_NODE',
'JOIN_GROUP',
'KICK_FROM_GROUP',
'LEAVE_GROUP',
'LINK_TO_QDN_RESOURCE',
'LIST_ATS',
'LIST_GROUPS',
'LIST_QDN_RESOURCES',
'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA', 'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
'OPEN_NEW_TAB',
'PUBLISH_MULTIPLE_QDN_RESOURCES',
'PUBLISH_QDN_RESOURCE',
'REGISTER_NAME',
'REMOVE_FOREIGN_SERVER',
'REMOVE_GROUP_ADMIN',
'SAVE_FILE',
'SEARCH_CHAT_MESSAGES',
'SEARCH_NAMES',
'SEARCH_QDN_RESOURCES',
'SEARCH_TRANSACTIONS',
'SELL_NAME',
'SEND_CHAT_MESSAGE',
'SEND_COIN',
'SET_CURRENT_FOREIGN_SERVER',
'SHOW_ACTIONS',
'SHOW_PDF_READER',
'SIGN_FOREIGN_FEES',
'SIGN_TRANSACTION',
'TRANSFER_ASSET', 'TRANSFER_ASSET',
'UPDATE_FOREIGN_FEE',
'UPDATE_GROUP',
'UPDATE_NAME',
'VOTE_ON_POLL',
]; ];
export const UIQortalRequests = [ export const UIQortalRequests = [
'GET_USER_ACCOUNT',
'DECRYPT_DATA',
'SEND_COIN',
'GET_LIST_ITEMS',
'ADD_LIST_ITEMS',
'DELETE_LIST_ITEM',
'VOTE_ON_POLL',
'CREATE_POLL',
'SEND_CHAT_MESSAGE',
'JOIN_GROUP',
'DEPLOY_AT',
'GET_USER_WALLET',
'GET_WALLET_BALANCE',
'GET_USER_WALLET_INFO',
'GET_CROSSCHAIN_SERVER_INFO',
'GET_TX_ACTIVITY_SUMMARY',
'GET_FOREIGN_FEE',
'UPDATE_FOREIGN_FEE',
'GET_SERVER_CONNECTION_HISTORY',
'SET_CURRENT_FOREIGN_SERVER',
'ADD_FOREIGN_SERVER', 'ADD_FOREIGN_SERVER',
'REMOVE_FOREIGN_SERVER', 'ADD_GROUP_ADMIN',
'GET_DAY_SUMMARY', 'ADD_LIST_ITEMS',
'ADMIN_ACTION',
'BAN_FROM_GROUP',
'BUY_NAME',
'CANCEL_GROUP_BAN',
'CANCEL_GROUP_INVITE',
'CANCEL_SELL_NAME',
'CANCEL_TRADE_SELL_ORDER',
'CREATE_AND_COPY_EMBED_LINK',
'CREATE_GROUP',
'CREATE_POLL',
'CREATE_TRADE_BUY_ORDER', 'CREATE_TRADE_BUY_ORDER',
'CREATE_TRADE_SELL_ORDER', 'CREATE_TRADE_SELL_ORDER',
'CANCEL_TRADE_SELL_ORDER',
'IS_USING_PUBLIC_NODE',
'ADMIN_ACTION',
'SIGN_TRANSACTION',
'OPEN_NEW_TAB',
'CREATE_AND_COPY_EMBED_LINK',
'DECRYPT_QORTAL_GROUP_DATA',
'DECRYPT_DATA_WITH_SHARING_KEY',
'DELETE_HOSTED_DATA',
'GET_HOSTED_DATA',
'SHOW_ACTIONS',
'REGISTER_NAME',
'UPDATE_NAME',
'LEAVE_GROUP',
'INVITE_TO_GROUP',
'KICK_FROM_GROUP',
'BAN_FROM_GROUP',
'CANCEL_GROUP_BAN',
'ADD_GROUP_ADMIN',
'REMOVE_GROUP_ADMIN',
'DECRYPT_AESGCM', 'DECRYPT_AESGCM',
'CANCEL_GROUP_INVITE', 'DECRYPT_DATA_WITH_SHARING_KEY',
'CREATE_GROUP', 'DECRYPT_DATA',
'GET_USER_WALLET_TRANSACTIONS', 'DECRYPT_QORTAL_GROUP_DATA',
'DELETE_HOSTED_DATA',
'DELETE_LIST_ITEM',
'DEPLOY_AT',
'GET_ARRR_SYNC_STATUS',
'GET_CROSSCHAIN_SERVER_INFO',
'GET_DAY_SUMMARY',
'GET_FOREIGN_FEE',
'GET_HOSTED_DATA',
'GET_LIST_ITEMS',
'GET_NODE_INFO', 'GET_NODE_INFO',
'GET_NODE_STATUS', 'GET_NODE_STATUS',
'GET_ARRR_SYNC_STATUS', 'GET_SERVER_CONNECTION_HISTORY',
'SHOW_PDF_READER', 'GET_TX_ACTIVITY_SUMMARY',
'UPDATE_GROUP', 'GET_USER_ACCOUNT',
'SELL_NAME', 'GET_USER_WALLET_INFO',
'CANCEL_SELL_NAME', 'GET_USER_WALLET_TRANSACTIONS',
'BUY_NAME', 'GET_USER_WALLET',
'SIGN_FOREIGN_FEES', 'GET_WALLET_BALANCE',
'INVITE_TO_GROUP',
'IS_USING_PUBLIC_NODE',
'JOIN_GROUP',
'KICK_FROM_GROUP',
'LEAVE_GROUP',
'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA', 'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
'OPEN_NEW_TAB',
'REGISTER_NAME',
'REMOVE_FOREIGN_SERVER',
'REMOVE_GROUP_ADMIN',
'SELL_NAME',
'SEND_CHAT_MESSAGE',
'SEND_COIN',
'SET_CURRENT_FOREIGN_SERVER',
'SHOW_ACTIONS',
'SHOW_PDF_READER',
'SIGN_FOREIGN_FEES',
'SIGN_TRANSACTION',
'TRANSFER_ASSET', 'TRANSFER_ASSET',
'UPDATE_FOREIGN_FEE',
'UPDATE_GROUP',
'UPDATE_NAME',
'VOTE_ON_POLL',
]; ];
async function retrieveFileFromIndexedDB(fileId) { async function retrieveFileFromIndexedDB(fileId) {
@ -658,7 +658,7 @@ export const useQortalMessageListener = (
setPath(pathUrl); setPath(pathUrl);
if (appName?.toLowerCase() === 'q-mail') { if (appName?.toLowerCase() === 'q-mail') {
window.sendMessage('addEnteredQmailTimestamp').catch((error) => { window.sendMessage('addEnteredQmailTimestamp').catch((error) => {
// error // TODO print error
}); });
} else if (appName?.toLowerCase() === 'q-wallets') { } else if (appName?.toLowerCase() === 'q-wallets') {
executeEvent('setLastEnteredTimestampPaymentEvent', {}); executeEvent('setLastEnteredTimestampPaymentEvent', {});

View File

@ -11,17 +11,32 @@
"seed_phrase": "add seed-phrase" "seed_phrase": "add seed-phrase"
}, },
"authenticate": "authenticate", "authenticate": "authenticate",
"block": "block",
"block_all": "block all",
"block_data": "block QDN data",
"block_name": "block name",
"block_txs": "block tsx",
"fetch_names": "fetch names",
"copy_address": "copy address",
"create_account": "create account", "create_account": "create account",
"create_qortal_account": "create your Qortal account by clicking <next>NEXT</next> below.", "create_qortal_account": "create your Qortal account by clicking <next>NEXT</next> below.",
"choose_password": "choose new password", "choose_password": "choose new password",
"download_account": "download account", "download_account": "download account",
"enter_amount": "please enter an amount greater than 0",
"enter_recipient": "please enter a recipient",
"enter_wallet_password": "please enter your wallet password",
"export_seedphrase": "export Seedphrase", "export_seedphrase": "export Seedphrase",
"insert_name_address": "please insert a name or address",
"publish_admin_secret_key": "publish admin secret key", "publish_admin_secret_key": "publish admin secret key",
"publish_group_secret_key": "publish group secret key", "publish_group_secret_key": "publish group secret key",
"reencrypt_key": "re-encrypt key", "reencrypt_key": "re-encrypt key",
"return_to_list": "return to list", "return_to_list": "return to list",
"setup_qortal_account": "set up your Qortal account" "setup_qortal_account": "set up your Qortal account",
"unblock": "unblock",
"unblock_name": "unblock name"
}, },
"address": "address",
"address_name": "address or name",
"advanced_users": "for advanced users", "advanced_users": "for advanced users",
"apikey": { "apikey": {
"alternative": "alternative: File select", "alternative": "alternative: File select",
@ -31,19 +46,29 @@
"key": "API key", "key": "API key",
"select_valid": "select a valid apikey" "select_valid": "select a valid apikey"
}, },
"blocked_users": "blocked users",
"build_version": "build version", "build_version": "build version",
"message": { "message": {
"error": { "error": {
"account_creation": "could not create account.", "account_creation": "could not create account.",
"address_not_existing": "address does not exist on blockchain",
"decrypt_data": "could not decrypt data", "decrypt_data": "could not decrypt data",
"field_not_found_json": "{{ field }} not found in JSON", "field_not_found_json": "{{ field }} not found in JSON",
"incorrect_password": "incorrect password", "incorrect_password": "incorrect password",
"invalid_secret_key": "secretKey is not valid", "invalid_secret_key": "secretKey is not valid",
"name_not_existing": "name does not exist",
"name_not_registered": "name not registered",
"unable_block_user": "unable to block user",
"unable_decrypt": "unable to decrypt", "unable_decrypt": "unable to decrypt",
"unable_reencrypt_secret_key": "unable to re-encrypt secret key" "unable_reencrypt_secret_key": "unable to re-encrypt secret key"
}, },
"generic": { "generic": {
"blocked_addresses": "blocked addresses- blocks processing of txs",
"blocked_names": "blocked names for QDN",
"blocking": "blocking {{ name }}",
"choose_block": "choose 'block txs' or 'all' to block chat messages",
"congrats_setup": "congrats, youre all set up!", "congrats_setup": "congrats, youre all set up!",
"decide_block": "decide what to block",
"no_account": "no accounts saved", "no_account": "no accounts saved",
"no_minimum_length": "there is no minimum length requirement", "no_minimum_length": "there is no minimum length requirement",
"no_secret_key_published": "no secret key published yet", "no_secret_key_published": "no secret key published yet",

View File

@ -70,6 +70,8 @@
"select_app_type": "select App Type", "select_app_type": "select App Type",
"select_category": "select Category", "select_category": "select Category",
"select_name_app": "select Name/App", "select_name_app": "select Name/App",
"send": "send",
"send_qort": "send QORT",
"set_avatar": "set avatar", "set_avatar": "set avatar",
"show": "show", "show": "show",
"show_poll": "show poll", "show_poll": "show poll",
@ -86,6 +88,7 @@
"admin": "admin", "admin": "admin",
"admin_other": "admins", "admin_other": "admins",
"all": "all", "all": "all",
"amount": "amount",
"announcement": "announcement", "announcement": "announcement",
"announcement_other": "announcements", "announcement_other": "announcements",
"api": "API", "api": "API",
@ -96,6 +99,7 @@
"apps_dashboard": "apps Dashboard", "apps_dashboard": "apps Dashboard",
"apps_official": "official Apps", "apps_official": "official Apps",
"attachment": "attachment", "attachment": "attachment",
"balance": "balance:",
"category": "category", "category": "category",
"category_other": "categories", "category_other": "categories",
"chat": "chat", "chat": "chat",
@ -169,8 +173,12 @@
"rating_option": "cannot find rating option", "rating_option": "cannot find rating option",
"save_qdn": "unable to save to QDN", "save_qdn": "unable to save to QDN",
"send_failed": "failed to send", "send_failed": "failed to send",
"unable_build_app": "unable to build private app",
"unable_download_image": "unable to download IMAGE. Please try again later by clicking the refresh button", "unable_download_image": "unable to download IMAGE. Please try again later by clicking the refresh button",
"unable_download_private_app": "unable to download private app",
"unable_decrypt_app": "unable to decrypt private app'",
"unable_encrypt_app": "unable to encrypt app. App not published'", "unable_encrypt_app": "unable to encrypt app. App not published'",
"unable_fetch_app": "unable to fetch app",
"unable_publish_app": "unable to publish app", "unable_publish_app": "unable to publish app",
"unable_publish_image": "unable to publish image", "unable_publish_image": "unable to publish image",
"unable_rate": "unable to rate", "unable_rate": "unable to rate",
@ -181,19 +189,23 @@
"already_voted": "you've already voted.", "already_voted": "you've already voted.",
"avatar_size": "{{ size }} KB max. for GIFS", "avatar_size": "{{ size }} KB max. for GIFS",
"building": "building", "building": "building",
"building_app": "building app",
"created_by": "created by {{ owner }}", "created_by": "created by {{ owner }}",
"buy_order_request": "the Application <br/><italic>{{hostname}}</italic> <br/><span>is requesting {{count}} buy order</span>", "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>", "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.", "devmode_local_node": "please use your local node for dev mode! Logout and use Local node.",
"downloading": "downloading", "downloading": "downloading",
"downloading_decrypting_app": "downloading and decrypting private app.",
"edited": "edited", "edited": "edited",
"editing_message": "editing message", "editing_message": "editing message",
"encrypted": "encrypted", "encrypted": "encrypted",
"encrypted_not": "not encrypted", "encrypted_not": "not encrypted",
"fee_qort": "fee: {{ message }} QORT", "fee_qort": "fee: {{ message }} QORT",
"fetching_data": "fetching app data",
"foreign_fee": "foreign fee: {{ message }}", "foreign_fee": "foreign fee: {{ message }}",
"mentioned": "mentioned", "mentioned": "mentioned",
"message_with_image": "this message already has an image", "message_with_image": "this message already has an image",
"most_recent_payment": "{{ count }} most recent payment",
"name_available": "{{ name }} is available", "name_available": "{{ name }} is available",
"name_benefits": "benefits of a name", "name_benefits": "benefits of a name",
"name_checking": "checking if name already exists", "name_checking": "checking if name already exists",
@ -207,9 +219,11 @@
"no_messages": "no messages", "no_messages": "no messages",
"no_minting_details": "cannot view minting details on the gateway", "no_minting_details": "cannot view minting details on the gateway",
"no_notifications": "no new notifications", "no_notifications": "no new notifications",
"no_payments": "no payments",
"no_pinned_changes": "you currently do not have any changes to your pinned apps", "no_pinned_changes": "you currently do not have any changes to your pinned apps",
"no_results": "no results", "no_results": "no results",
"one_app_per_name": "note: Currently, only one App and Website is allowed per Name.", "one_app_per_name": "note: Currently, only one App and Website is allowed per Name.",
"opened": "opened",
"overwrite_qdn": "overwrite to QDN", "overwrite_qdn": "overwrite to QDN",
"password_confirm": "please confirm a password", "password_confirm": "please confirm a password",
"password_enter": "please enter a password", "password_enter": "please enter a password",
@ -236,6 +250,7 @@
"unsaved_changes": "you have unsaved changes to your pinned apps. Save them to QDN.", "unsaved_changes": "you have unsaved changes to your pinned apps. Save them to QDN.",
"updating": "updating" "updating": "updating"
}, },
"message": "message",
"question": { "question": {
"accept_vote_on_poll": "do you accept this VOTE_ON_POLL transaction? POLLS are public!", "accept_vote_on_poll": "do you accept this VOTE_ON_POLL transaction? POLLS are public!",
"logout": "are you sure you would like to logout?", "logout": "are you sure you would like to logout?",
@ -250,7 +265,8 @@
"rate_app": "would you like to rate this app a rating of {{ rate }}?. It will create a POLL tx.", "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?", "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_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?" "reset_qdn": "don't like your current local changes? Would you like to reset to your saved QDN pinned apps?",
"transfer_qort": "would you like to transfer {{ amount }} QORT"
}, },
"status": { "status": {
"minting": "(minting)", "minting": "(minting)",
@ -271,6 +287,7 @@
"minting_status": "minting status", "minting_status": "minting status",
"name": "name", "name": "name",
"name_app": "name/App", "name_app": "name/App",
"new_post_in": "new post in {{ title }}",
"none": "none", "none": "none",
"option": "option", "option": "option",
"option_other": "options", "option_other": "options",
@ -288,8 +305,11 @@
"about": "about this Q-App", "about": "about this Q-App",
"q_mail": "q-mail", "q_mail": "q-mail",
"q_manager": "q-manager", "q_manager": "q-manager",
"q_sandbox": "q-Sandbox" "q_sandbox": "q-Sandbox",
"q_wallets": "q-Wallets"
}, },
"receiver": "receiver",
"sender": "sender",
"server": "server", "server": "server",
"settings": "settings", "settings": "settings",
"sort": { "sort": {
@ -309,9 +329,11 @@
"hour_one": "{{count}} hour", "hour_one": "{{count}} hour",
"hour_other": "{{count}} hours", "hour_other": "{{count}} hours",
"minute_one": "{{count}} minute", "minute_one": "{{count}} minute",
"minute_other": "{{count}} minutes" "minute_other": "{{count}} minutes",
"time": "time"
}, },
"title": "title", "title": "title",
"to": "to",
"tutorial": "tutorial", "tutorial": "tutorial",
"user_lookup": "user lookup", "user_lookup": "user lookup",
"vote": "vote", "vote": "vote",

View File

@ -99,6 +99,7 @@
"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", "only_private_groups": "only private groups will be shown",
"pending_join_requests": "{{ group }} has {{ count }} pending join requests",
"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!",