From 8623500d88206045594dda8a575f4743f8008374 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sun, 18 May 2025 21:57:12 +0200 Subject: [PATCH 01/13] Add useTranslation --- src/components/MainAvatar.tsx | 3 +++ src/components/QortPayment.tsx | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index 60a32b8..6058558 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -16,6 +16,7 @@ import { getFee } from '../background'; import { fileToBase64 } from '../utils/fileReading'; import { LoadingButton } from '@mui/lab'; import ErrorIcon from '@mui/icons-material/Error'; +import { useTranslation } from 'react-i18next'; export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { const [hasAvatar, setHasAvatar] = useState(false); @@ -40,6 +41,8 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { const open = Boolean(anchorEl); const id = open ? 'avatar-img' : undefined; + const { t } = useTranslation(['auth', 'core', 'group']); + const checkIfAvatarExists = async () => { try { const identifier = `qortal_avatar`; diff --git a/src/components/QortPayment.tsx b/src/components/QortPayment.tsx index dab6ec2..48074c6 100644 --- a/src/components/QortPayment.tsx +++ b/src/components/QortPayment.tsx @@ -11,9 +11,11 @@ import BoundedNumericTextField from '../common/BoundedNumericTextField'; import { PasswordField } from './PasswordField/PasswordField'; import { ErrorText } from './ErrorText/ErrorText'; import { getFee } from '../background'; +import { useTranslation } from 'react-i18next'; export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { const theme = useTheme(); + const { t } = useTranslation(['auth', 'core', 'group']); const [paymentTo, setPaymentTo] = useState(defaultPaymentTo); const [paymentAmount, setPaymentAmount] = useState(0); const [paymentPassword, setPaymentPassword] = useState(''); From 253370a0507ab5e16e4759830fbaa35fd8809349 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Mon, 19 May 2025 22:42:08 +0200 Subject: [PATCH 02/13] Remove bracket --- src/components/Apps/AppsNavBarDesktop.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Apps/AppsNavBarDesktop.tsx b/src/components/Apps/AppsNavBarDesktop.tsx index 1cfe097..7afa4e1 100644 --- a/src/components/Apps/AppsNavBarDesktop.tsx +++ b/src/components/Apps/AppsNavBarDesktop.tsx @@ -367,6 +367,7 @@ export const AppsNavBarDesktop = ({ disableBack }) => { }} /> + { : theme.palette.text.primary, }, }} - primary={`${ + primary={ isSelectedAppPinned ? t('core:action.unpin_app', { postProcess: 'capitalizeFirstChar', @@ -385,7 +386,7 @@ export const AppsNavBarDesktop = ({ disableBack }) => { : t('core:action.pin_app', { postProcess: 'capitalizeFirstChar', }) - }}`} + } /> From d00265d87844b85fd093990f4490a4f70ec86910 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Mon, 19 May 2025 23:35:05 +0200 Subject: [PATCH 03/13] Add translations --- src/App.tsx | 5 +- src/components/Apps/TabComponent.tsx | 1 + src/components/Chat/GroupAvatar.tsx | 1 + src/components/Desktop/DesktopSideBar.tsx | 8 +- src/components/Group/AddGroup.tsx | 1 + src/components/Group/AddGroupList.tsx | 18 +++- src/components/Group/BlockedUsersModal.tsx | 10 +- src/components/Group/Forum/GroupMail.tsx | 4 +- src/components/Group/Forum/NewThread.tsx | 1 - .../Group/Forum/ShowMessageWithoutModal.tsx | 5 +- src/components/Group/Forum/Thread.tsx | 7 ++ src/components/MainAvatar.tsx | 65 ++++++++++--- src/components/UserLookup.tsx/UserLookup.tsx | 94 +++++++++++++++---- src/components/WrapperUserAction.tsx | 30 +++--- src/i18n/locales/en/auth.json | 7 ++ src/i18n/locales/en/core.json | 11 +++ 16 files changed, 211 insertions(+), 57 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 90db808..feb0d66 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1697,10 +1697,11 @@ function App() { style={{ fontSize: '14px', fontWeight: 700, - textTransform: 'uppercase', }} > - {t('core:user_lookup')} + {t('core:user_lookup', { + postProcess: 'capitalizeAll', + })} } placement="left" diff --git a/src/components/Apps/TabComponent.tsx b/src/components/Apps/TabComponent.tsx index 2558a9f..a7584c0 100644 --- a/src/components/Apps/TabComponent.tsx +++ b/src/components/Apps/TabComponent.tsx @@ -40,6 +40,7 @@ const TabComponent = ({ isSelected, app }) => { }} /> )} + {app?.isPrivate && !app?.privateAppProperties?.logo ? ( @@ -55,8 +55,8 @@ export const DesktopSideBar = ({ { goToHome(); diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx index 2a83e5a..53fc1c9 100644 --- a/src/components/Group/AddGroup.tsx +++ b/src/components/Group/AddGroup.tsx @@ -593,6 +593,7 @@ export const AddGroup = ({ address, open, setOpen }) => { )} + {value === 1 && ( { const { show } = useContext(MyContext); const [memberGroups] = useAtom(memberGroupsAtom); - const setTxList = useSetAtom(txListAtom); - const { t } = useTranslation(['auth', 'core', 'group']); const [groups, setGroups] = useState([]); 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) => { setInfoSnack({ type: 'error', - message: error.message || 'An error occurred', + message: + error.message || + t('core:message.error.generic', { + postProcess: 'capitalizeFirst', + }), }); setOpenSnack(true); rej(error); @@ -248,10 +250,14 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { })}{' '} {group?.groupName} + {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', + })} + { + handlePopoverOpen(event, index)} > @@ -274,6 +281,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { }} /> )} + {group?.isOpen === true && ( { }} /> )} + + { const theme = useTheme(); + const { t } = useTranslation(['auth', 'core', 'group']); const [isOpenBlockedModal, setIsOpenBlockedModal] = useAtom( isOpenBlockedModalAtom ); - const [hasChanged, setHasChanged] = useState(false); const [value, setValue] = useState(''); const [addressesWithNames, setAddressesWithNames] = useState({}); @@ -95,7 +96,12 @@ export const BlockedUsersModal = () => { if (!isAddress) { const response = await fetch(`${getBaseApiReact()}/names/${valUser}`); 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: 'capitalizeFirst', + }) + ); if (data?.owner) { userAddress = data.owner; userName = valUser; diff --git a/src/components/Group/Forum/GroupMail.tsx b/src/components/Group/Forum/GroupMail.tsx index 3e0783c..afa6e91 100644 --- a/src/components/Group/Forum/GroupMail.tsx +++ b/src/components/Group/Forum/GroupMail.tsx @@ -271,6 +271,7 @@ export const GroupMail = ({ }, [allThreads, isPrivate] ); + const getMailMessages = useCallback( async (groupId: string, members: any) => { try { @@ -385,7 +386,6 @@ export const GroupMail = ({ }, [getMailMessages, groupId, members, secretKey, isPrivate]); const interval = useRef(null); - const firstMount = useRef(false); const filterModeRef = useRef(''); @@ -575,6 +575,7 @@ export const GroupMail = ({ }} > + {filterOptions?.map((filter) => { return ( @@ -796,6 +797,7 @@ export const GroupMail = ({ postProcess: 'capitalizeFirstChar', })} + { return ( { {formatTimestampForum(message?.created)} +
0) { 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=${ fullArrayMsg[fullArrayMsg.length - 1].created }`; + const responseOlder = await fetch(urlOlder, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); + const responseDataOlder = await responseOlder.json(); if (responseDataOlder.length > 0) { setHasLastPage(true); @@ -321,6 +325,7 @@ export const Thread = ({ }, [messages, secretKey] ); + const getMessages = useCallback(async () => { if ( !currentThread || @@ -337,6 +342,7 @@ export const Thread = ({ groupInfo?.groupId, isPrivate, ]); + const firstMount = useRef(false); const saveTimestamp = useCallback((currentThread: any, username?: string) => { @@ -613,6 +619,7 @@ export const Thread = ({ })} + {/* Conditionally render the scroll buttons */} {showScrollButton && (isAtBottom ? ( diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index 6058558..d81c029 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -68,16 +68,26 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { const publishAvatar = async () => { try { - // TODO translate const fee = await getFee('ARBITRARY'); + 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({ - message: 'Would you like to publish an avatar?', + message: t('core:message.question.publish_avatar', { + postProcess: 'capitalizeFirstChar', + }), publishFee: fee.fee + ' QORT', }); + setIsLoading(true); const avatarBase64 = await fileToBase64(avatarFile); + await new Promise((res, rej) => { window .sendMessage('publishOnQDN', { @@ -93,7 +103,12 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { rej(response.error); }) .catch((error) => { - rej(error.message || 'An error occurred'); + rej( + error.message || + t('core:message.error.generic', { + postProcess: 'capitalizeFirst', + }) + ); }); }); setAvatarFile(null); @@ -125,6 +140,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { > {myName?.charAt(0)} + { opacity: 0.5, }} > - change avatar + {t('core:action.change_avatar', { + postProcess: 'capitalizeFirstChar', + })} + { > {myName?.charAt(0)} + { opacity: 0.5, }} > - change avatar + {t('core:action.change_avatar', { + postProcess: 'capitalizeFirstChar', + })} + { opacity: 0.5, }} > - set avatar + {t('core:action.set_avatar', { postProcess: 'capitalizeFirstChar' })} + { ); }; +// TODO the following part is the same as in GroupAvatar.tsx const PopoverComp = ({ avatarFile, setAvatarFile, @@ -228,6 +253,8 @@ const PopoverComp = ({ myName, }) => { const theme = useTheme(); + const { t } = useTranslation(['auth', 'core', 'group']); + return ( - (500 KB max. for GIFS){' '} + {t('core:message.generic.avatar_size', { + size: 500, // TODO magic number + postProcess: 'capitalizeFirstChar', + })} + setAvatarFile(file)}> - + + {avatarFile?.name} + + {!myName && ( - A registered name is required to set an avatar + {t('core:message.generic.avatar_registered_name', { + postProcess: 'capitalizeFirstChar', + })} )} + - Publish avatar + {t('group:action.publish_avatar', { + postProcess: 'capitalizeFirstChar', + })} diff --git a/src/components/UserLookup.tsx/UserLookup.tsx b/src/components/UserLookup.tsx/UserLookup.tsx index ee0b8c9..b1bf858 100644 --- a/src/components/UserLookup.tsx/UserLookup.tsx +++ b/src/components/UserLookup.tsx/UserLookup.tsx @@ -33,6 +33,7 @@ import { unsubscribeFromEvent, } from '../../utils/events'; import { useNameSearch } from '../../hooks/useNameSearch'; +import { useTranslation } from 'react-i18next'; function formatAddress(str) { if (str.length <= 12) return str; @@ -45,6 +46,7 @@ function formatAddress(str) { export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { const theme = useTheme(); + const { t } = useTranslation(['auth', 'core', 'group']); const [nameOrAddress, setNameOrAddress] = useState(''); const [inputValue, setInputValue] = useState(''); const { results, isLoading } = useNameSearch(inputValue); @@ -64,13 +66,27 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { const inputAddressOrName = messageAddressOrName || nameOrAddress; if (!inputAddressOrName?.trim()) - throw new Error('Please insert a name or address'); + throw new Error( + t('auth:action.insert_name_address', { + postProcess: 'capitalizeFirst', + }) + ); + 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: 'capitalizeFirst', + }) + ); const addressInfoRes = await getAddressInfo(owner); if (!addressInfoRes?.publicKey) { - throw new Error('Address does not exist on blockchain'); + throw new Error( + t('auth:message.error.address_not_existing', { + postProcess: 'capitalizeFirst', + }) + ); } const name = await getNameInfo(owner); @@ -175,7 +191,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { autoFocus autoComplete="off" {...params} - label="Address or Name" + label={t('auth:address_name', { + postProcess: 'capitalizeFirst', + })} onKeyDown={(e) => { if (e.key === 'Enter' && nameOrAddress) { lookupFunc(inputValue); @@ -200,6 +218,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { /> + { {errorMessage} )} + {isLoadingUser && ( { /> )} + {!isLoadingUser && addressInfo && ( <> @@ -265,7 +286,10 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { textAlign: 'center', }} > - {addressInfo?.name ?? 'Name not registered'} + {addressInfo?.name ?? + t('auth:message.error.name_not_registered', { + postProcess: 'capitalizeFirst', + })} @@ -307,7 +331,8 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { textAlign: 'center', }} > - Level {addressInfo?.level} + {t('core:level', { postProcess: 'capitalizeFirst' })}{' '} + {addressInfo?.level} @@ -336,8 +361,11 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { flexShrink: 0, }} > - Address + + {t('auth:address', { postProcess: 'capitalizeFirst' })} + + { fontWeight: 700, }} > - copy address + {t('auth:action.copy_address', { + postProcess: 'capitalizeFirst', + })} } placement="bottom" @@ -391,7 +421,10 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { width: '100%', }} > - Balance + + {t('core:balance', { postProcess: 'capitalizeFirst' })} + + {addressInfo?.balance} @@ -406,7 +439,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { }); }} > - Send QORT + {t('core:action.send_qort', { + postProcess: 'capitalizeFirst', + })} @@ -440,7 +475,12 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { padding: '15px', }} > - 20 most recent payments + + {t('core:message.generic.most_recent_payment', { + count: 20, + postProcess: 'capitalizeFirst', + })} + @@ -452,17 +492,29 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { width: '100%', }} > - No payments + + {t('core:message.generic.no_payments', { + postProcess: 'capitalizeFirst', + })} + )} - Sender - Reciver - Amount - Time + + {t('core:sender', { postProcess: 'capitalizeFirst' })} + + + {t('core:receiver', { postProcess: 'capitalizeFirst' })} + + + {t('core:amount', { postProcess: 'capitalizeFirst' })} + + + {t('core:time', { postProcess: 'capitalizeFirst' })} + @@ -479,7 +531,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { fontWeight: 700, }} > - copy address + {t('auth:action.copy_address', { + postProcess: 'capitalizeFirst', + })} } placement="bottom" @@ -522,7 +576,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { fontWeight: 700, }} > - copy address + {t('auth:action.copy_address', { + postProcess: 'capitalizeFirst', + })} } placement="bottom" @@ -552,7 +608,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { + {payment?.amount} + {formatTimestamp(payment?.timestamp)} diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index 07b5d45..afcb686 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -10,9 +10,11 @@ import { executeEvent } from '../utils/events'; import { MyContext } from '../App'; import { useAtom } from 'jotai'; import { isRunningPublicNodeAtom } from '../atoms/global'; +import { useTranslation } from 'react-i18next'; export const WrapperUserAction = ({ children, address, name, disabled }) => { const theme = useTheme(); + const { t } = useTranslation(['auth', 'core', 'group']); const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom); const [anchorEl, setAnchorEl] = useState(null); @@ -96,7 +98,7 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { justifyContent: 'flex-start', }} > - Message + {t('core:message.message', { postProcess: 'capitalizeFirst' })} {/* Option 2: Send QORT */} @@ -114,8 +116,11 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { justifyContent: 'flex-start', }} > - Send QORT + {t('core:action.send_qort', { + postProcess: 'capitalizeFirst', + })} + + {!isRunningPublicNode && ( @@ -165,6 +175,7 @@ const BlockUser = ({ address, name, handleClose }) => { const { isUserBlocked, addToBlockList, removeBlockFromList } = useContext(MyContext); const theme = useTheme(); + const { t } = useTranslation(['auth', 'core', 'group']); useEffect(() => { if (!address) return; @@ -180,12 +191,6 @@ const BlockUser = ({ address, name, handleClose }) => { executeEvent('blockUserFromOutside', { user: address, }); - // if(isAlreadyBlocked === true){ - // await removeBlockFromList(address, name) - // } else if(isAlreadyBlocked === false) { - // await addToBlockList(address, name) - // } - // executeEvent('updateChatMessagesWithBlocks', true) } catch (error) { console.error(error); } finally { @@ -202,8 +207,9 @@ const BlockUser = ({ address, name, handleClose }) => { {(isAlreadyBlocked === null || isLoading) && ( )} - {isAlreadyBlocked && 'Unblock name'} - {isAlreadyBlocked === false && 'Block name'} + {isAlreadyBlocked && + t('core:action.unblock_name', { postProcess: 'capitalizeFirst' })} + {isAlreadyBlocked === false && t('core:action.block_name', { postProcess: 'capitalizeFirst' })}} ); }; diff --git a/src/i18n/locales/en/auth.json b/src/i18n/locales/en/auth.json index a62b460..a1dca21 100644 --- a/src/i18n/locales/en/auth.json +++ b/src/i18n/locales/en/auth.json @@ -11,17 +11,21 @@ "seed_phrase": "add seed-phrase" }, "authenticate": "authenticate", + "copy_address": "copy address", "create_account": "create account", "create_qortal_account": "create your Qortal account by clicking NEXT below.", "choose_password": "choose new password", "download_account": "download account", "export_seedphrase": "export Seedphrase", + "insert_name_address": "please insert a name or address", "publish_admin_secret_key": "publish admin secret key", "publish_group_secret_key": "publish group secret key", "reencrypt_key": "re-encrypt key", "return_to_list": "return to list", "setup_qortal_account": "set up your Qortal account" }, + "address": "address", + "address_name": "address or name", "advanced_users": "for advanced users", "apikey": { "alternative": "alternative: File select", @@ -35,10 +39,13 @@ "message": { "error": { "account_creation": "could not create account.", + "address_not_existing": "address does not exist on blockchain", "decrypt_data": "could not decrypt data", "field_not_found_json": "{{ field }} not found in JSON", "incorrect_password": "incorrect password", "invalid_secret_key": "secretKey is not valid", + "name_not_existing": "name does not exist", + "name_not_registered": "name not registered", "unable_decrypt": "unable to decrypt", "unable_reencrypt_secret_key": "unable to re-encrypt secret key" }, diff --git a/src/i18n/locales/en/core.json b/src/i18n/locales/en/core.json index ba9d509..d91851d 100644 --- a/src/i18n/locales/en/core.json +++ b/src/i18n/locales/en/core.json @@ -8,6 +8,7 @@ "access_app": "access app", "backup_account": "backup account", "backup_wallet": "backup wallet", + "block_name": "block name", "cancel": "cancel", "cancel_invitation": "cancel invitation", "change": "change", @@ -70,12 +71,14 @@ "select_app_type": "select App Type", "select_category": "select Category", "select_name_app": "select Name/App", + "send_qort": "send QORT", "set_avatar": "set avatar", "show": "show", "show_poll": "show poll", "start_minting": "start minting", "start_typing": "start typing here...", "transfer_qort": "Transfer QORT", + "unblock_name": "unblock name", "unpin": "unpin", "unpin_app": "unpin app", "unpin_from_dashboard": "unpin from dashboard", @@ -86,6 +89,7 @@ "admin": "admin", "admin_other": "admins", "all": "all", + "amount": "amount", "announcement": "announcement", "announcement_other": "announcements", "api": "API", @@ -96,6 +100,7 @@ "apps_dashboard": "apps Dashboard", "apps_official": "official Apps", "attachment": "attachment", + "balance": "balance", "category": "category", "category_other": "categories", "chat": "chat", @@ -194,6 +199,7 @@ "foreign_fee": "foreign fee: {{ message }}", "mentioned": "mentioned", "message_with_image": "this message already has an image", + "most_recent_payment": "{{ count }} most recent payment", "name_available": "{{ name }} is available", "name_benefits": "benefits of a name", "name_checking": "checking if name already exists", @@ -207,6 +213,7 @@ "no_messages": "no messages", "no_minting_details": "cannot view minting details on the gateway", "no_notifications": "no new notifications", + "no_payments": "no payments", "no_pinned_changes": "you currently do not have any changes to your pinned apps", "no_results": "no results", "one_app_per_name": "note: Currently, only one App and Website is allowed per Name.", @@ -236,6 +243,7 @@ "unsaved_changes": "you have unsaved changes to your pinned apps. Save them to QDN.", "updating": "updating" }, + "message": "message", "question": { "accept_vote_on_poll": "do you accept this VOTE_ON_POLL transaction? POLLS are public!", "logout": "are you sure you would like to logout?", @@ -290,6 +298,8 @@ "q_manager": "q-manager", "q_sandbox": "q-Sandbox" }, + "receiver": "receiver", + "sender": "sender", "server": "server", "settings": "settings", "sort": { @@ -311,6 +321,7 @@ "minute_one": "{{count}} minute", "minute_other": "{{count}} minutes" }, + "time": "time", "title": "title", "tutorial": "tutorial", "user_lookup": "user lookup", From ab1e36fd4edd87892d3ca54076b993861544c366 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Mon, 19 May 2025 23:53:43 +0200 Subject: [PATCH 04/13] Add translations --- src/components/Group/BlockedUsersModal.tsx | 1 + src/components/Group/Group.tsx | 1 + src/components/Group/GroupInvites.tsx | 9 ++++---- src/components/Group/GroupJoinRequests.tsx | 6 ++++- src/components/Group/GroupList.tsx | 17 +++++++------- src/components/Group/HomeDesktop.tsx | 22 ++++++------------- src/components/Group/ListOfBans.tsx | 8 ++++++- .../Group/ListOfGroupPromotions.tsx | 16 ++++++-------- src/components/Group/ListOfJoinRequests.tsx | 12 +++++----- .../Group/ListOfThreadPostsWatched.tsx | 4 +++- src/components/Group/ManageMembers.tsx | 18 +++++++-------- src/components/Group/QMailMessages.tsx | 1 + src/components/Group/Settings.tsx | 2 ++ src/components/Group/WalletsAppWrapper.tsx | 6 ++++- src/components/UserLookup.tsx/UserLookup.tsx | 2 +- src/i18n/locales/en/core.json | 7 +++--- 16 files changed, 72 insertions(+), 60 deletions(-) diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index 4b7a622..aa0ac0a 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -138,6 +138,7 @@ export const BlockedUsersModal = () => { } catch (error) { setOpenSnackGlobal(true); setInfoSnackCustom({ + //TODO translate type: 'error', message: error?.message || 'Unable to block user', }); diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 6e8b56c..165a8ee 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -2388,6 +2388,7 @@ export const Group = ({ : 'flex', }} > + { const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]); const [isExpanded, setIsExpanded] = useState(false); - const [loading, setLoading] = useState(true); + const { t } = useTranslation(['auth', 'core', 'group']); + const theme = useTheme(); const getJoinRequests = async () => { try { @@ -37,9 +38,6 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => { } }; - const { t } = useTranslation(['auth', 'core', 'group']); - const theme = useTheme(); - useEffect(() => { if (myAddress) { getJoinRequests(); @@ -75,6 +73,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => { {groupsWithJoinRequests?.length > 0 && ` (${groupsWithJoinRequests?.length})`} + {isExpanded ? ( { )} + {!loading && groupsWithJoinRequests.length === 0 && ( { )} + 0 && ` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`} + {isExpanded ? ( )} + )} + {!loading && (filteredJoinRequests.length === 0 || filteredJoinRequests?.filter((group) => group?.data?.length > 0) @@ -212,6 +215,7 @@ export const GroupJoinRequests = ({ )} + diff --git a/src/components/Group/GroupList.tsx b/src/components/Group/GroupList.tsx index b9b0433..156d744 100644 --- a/src/components/Group/GroupList.tsx +++ b/src/components/Group/GroupList.tsx @@ -29,7 +29,6 @@ import { isRunningPublicNodeAtom, timestampEnterDataSelector, } from '../../atoms/global'; - import { timeDifferenceForNotificationChats } from './Group'; import { useAtom, useAtomValue } from 'jotai'; import { useTranslation } from 'react-i18next'; @@ -56,23 +55,23 @@ export const GroupList = ({ return (
@@ -296,7 +295,7 @@ const GroupItem = React.memo( theme.palette.text.primary, fontSize: '16px', }, - }} // Change the color of the primary text + }} secondaryTypographyProps={{ style: { color: diff --git a/src/components/Group/HomeDesktop.tsx b/src/components/Group/HomeDesktop.tsx index e5e0d4e..a5cf85e 100644 --- a/src/components/Group/HomeDesktop.tsx +++ b/src/components/Group/HomeDesktop.tsx @@ -1,5 +1,5 @@ import { Box, Divider, Typography, useTheme } from '@mui/material'; -import React from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Spacer } from '../../common/Spacer'; import { ThingsToDoInitial } from './ThingsToDoInitial'; import { GroupJoinRequests } from './GroupJoinRequests'; @@ -28,28 +28,28 @@ export const HomeDesktop = ({ setDesktopViewMode, desktopViewMode, }) => { - const [checked1, setChecked1] = React.useState(false); - const [checked2, setChecked2] = React.useState(false); + const [checked1, setChecked1] = useState(false); + const [checked2, setChecked2] = useState(false); const { t } = useTranslation(['auth', 'core', 'group']); const theme = useTheme(); - React.useEffect(() => { + useEffect(() => { if (balance && +balance >= 6) { setChecked1(true); } }, [balance]); - React.useEffect(() => { + useEffect(() => { if (name) setChecked2(true); }, [name]); - const isLoaded = React.useMemo(() => { + const isLoaded = useMemo(() => { if (userInfo !== null) return true; return false; }, [userInfo]); - const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => { + const hasDoneNameAndBalanceAndIsLoaded = useMemo(() => { if (isLoaded && checked1 && checked2) return true; return false; }, [checked1, isLoaded, checked2]); @@ -136,14 +136,6 @@ export const HomeDesktop = ({ {desktopViewMode === 'home' && ( <> - {/* - - */} {hasDoneNameAndBalanceAndIsLoaded && ( <> { const handleCancelBan = async (address) => { try { const fee = await getFee('CANCEL_GROUP_BAN'); + await show({ message: t('core:message.question.perform_transaction', { action: 'CANCEL_GROUP_BAN', @@ -94,6 +95,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => { }), publishFee: fee.fee + ' QORT', }); + setIsLoadingUnban(true); new Promise((res, rej) => { window @@ -125,7 +127,11 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => { .catch((error) => { setInfoSnack({ type: 'error', - message: error.message || 'An error occurred', + message: + error.message || + t('core:message.error.generic', { + postProcess: 'capitalizeFirst', + }), }); setOpenSnack(true); rej(error); diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index eb35dfe..a133c30 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -1,10 +1,4 @@ -import React, { - useCallback, - useContext, - useEffect, - useRef, - useState, -} from 'react'; +import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { Avatar, Box, @@ -88,7 +82,7 @@ export const ListOfGroupPromotions = () => { const [promotionTimeInterval, setPromotionTimeInterval] = useAtom( promotionTimeIntervalAtom ); - const [isExpanded, setIsExpanded] = React.useState(false); + const [isExpanded, setIsExpanded] = useState(false); const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const [fee, setFee] = useState(null); @@ -101,7 +95,7 @@ export const ListOfGroupPromotions = () => { const listRef = useRef(null); const rowVirtualizer = useVirtualizer({ count: promotions.length, - getItemKey: React.useCallback( + getItemKey: useCallback( (index) => promotions[index]?.identifier, [promotions] ), @@ -495,6 +489,7 @@ export const ListOfGroupPromotions = () => { )} + {!loading && promotions.length === 0 && ( { )} +
{ }} /> )} + {promotion?.isOpen === true && ( { }} /> )} +
diff --git a/src/components/Group/ListOfThreadPostsWatched.tsx b/src/components/Group/ListOfThreadPostsWatched.tsx index fc94ee5..024612f 100644 --- a/src/components/Group/ListOfThreadPostsWatched.tsx +++ b/src/components/Group/ListOfThreadPostsWatched.tsx @@ -117,6 +117,7 @@ export const ListOfThreadPostsWatched = () => { )} + {!loading && posts.length === 0 && ( { )} + {posts?.length > 0 && ( { } > - diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index 419009e..22c6d0c 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -367,9 +367,9 @@ export const ManageMembers = ({ {value === 0 && ( )} @@ -273,7 +275,7 @@ export const PollCard = ({ fontSize: '18px', }} > - {t('core:option_other', { postProcess: 'capitalizeFirst' })} + {t('core:option_other', { postProcess: 'capitalizeFirstChar' })} - {t('core:action.vote', { postProcess: 'capitalizeFirst' })} + {t('core:action.vote', { postProcess: 'capitalizeFirstChar' })} {t('core:message.generic.already_voted', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} @@ -350,7 +352,7 @@ export const PollCard = ({ }} > {t('core:message.generic.processing_transaction', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} )} @@ -361,8 +363,8 @@ export const PollCard = ({ }} > {showResults - ? t('core:action.hide', { postProcess: 'capitalizeFirst' }) - : t('core:action.close', { postProcess: 'capitalizeFirst' })} + ? t('core:action.hide', { postProcess: 'capitalizeFirstChar' }) + : t('core:action.close', { postProcess: 'capitalizeFirstChar' })} diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx index cd7bcf4..64c4b3b 100644 --- a/src/components/Group/AddGroupList.tsx +++ b/src/components/Group/AddGroupList.tsx @@ -190,7 +190,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { message: error.message || t('core:message.error.generic', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', }), }); setOpenSnack(true); diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index aa0ac0a..de9ec80 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -99,7 +99,7 @@ export const BlockedUsersModal = () => { if (!data?.owner) throw new Error( t('auth:message.error.name_not_existing', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', }) ); if (data?.owner) { @@ -138,9 +138,12 @@ export const BlockedUsersModal = () => { } catch (error) { setOpenSnackGlobal(true); setInfoSnackCustom({ - //TODO translate type: 'error', - message: error?.message || 'Unable to block user', + message: + error?.message || + t('auth:message.error.unable_block_user', { + postProcess: 'capitalizeFirstChar', + }), }); } }; @@ -168,7 +171,9 @@ export const BlockedUsersModal = () => { aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" > - Blocked Users + + {t('auth:blocked_users', { postProcess: 'capitalizeFirstChar' })} + { message: error.message || t('core:message.error.generic', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', }), }); setOpenSnack(true); diff --git a/src/components/Group/WalletsAppWrapper.tsx b/src/components/Group/WalletsAppWrapper.tsx index ef2b66c..ee9885a 100644 --- a/src/components/Group/WalletsAppWrapper.tsx +++ b/src/components/Group/WalletsAppWrapper.tsx @@ -90,7 +90,9 @@ export const WalletsAppWrapper = () => { }} > - {t('core:q_apps.q_wallets', { postProcess: 'capitalizeFirst' })} + {t('core:q_apps.q_wallets', { + postProcess: 'capitalizeFirstChar', + })} diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index d81c029..be55714 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -106,7 +106,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { rej( error.message || t('core:message.error.generic', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', }) ); }); diff --git a/src/components/UserLookup.tsx/UserLookup.tsx b/src/components/UserLookup.tsx/UserLookup.tsx index f227507..675d661 100644 --- a/src/components/UserLookup.tsx/UserLookup.tsx +++ b/src/components/UserLookup.tsx/UserLookup.tsx @@ -68,7 +68,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { if (!inputAddressOrName?.trim()) throw new Error( t('auth:action.insert_name_address', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', }) ); @@ -76,7 +76,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { if (!owner) throw new Error( t('auth:message.error.name_not_existing', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', }) ); @@ -84,7 +84,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { if (!addressInfoRes?.publicKey) { throw new Error( t('auth:message.error.address_not_existing', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', }) ); } @@ -192,7 +192,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { autoComplete="off" {...params} label={t('auth:address_name', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} onKeyDown={(e) => { if (e.key === 'Enter' && nameOrAddress) { @@ -288,7 +288,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { > {addressInfo?.name ?? t('auth:message.error.name_not_registered', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} @@ -331,7 +331,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { textAlign: 'center', }} > - {t('core:level', { postProcess: 'capitalizeFirst' })}{' '} + {t('core:level', { postProcess: 'capitalizeFirstChar' })}{' '} {addressInfo?.level} @@ -362,7 +362,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { }} > - {t('auth:address', { postProcess: 'capitalizeFirst' })} + {t('auth:address', { + postProcess: 'capitalizeFirstChar', + })} @@ -376,7 +378,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { }} > {t('auth:action.copy_address', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} } @@ -422,7 +424,9 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { }} > - {t('core:balance', { postProcess: 'capitalizeFirst' })} + {t('core:balance', { + postProcess: 'capitalizeFirstChar', + })} {addressInfo?.balance} @@ -440,7 +444,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { }} > {t('core:action.send_qort', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} @@ -478,7 +482,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { {t('core:message.generic.most_recent_payment', { count: 20, - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} @@ -494,7 +498,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { > {t('core:message.generic.no_payments', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} @@ -504,16 +508,20 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { - {t('core:sender', { postProcess: 'capitalizeFirst' })} + {t('core:sender', { postProcess: 'capitalizeFirstChar' })} - {t('core:receiver', { postProcess: 'capitalizeFirst' })} + {t('core:receiver', { + postProcess: 'capitalizeFirstChar', + })} - {t('core:amount', { postProcess: 'capitalizeFirst' })} + {t('core:amount', { postProcess: 'capitalizeFirstChar' })} - {t('core:time.time', { postProcess: 'capitalizeFirst' })} + {t('core:time.time', { + postProcess: 'capitalizeFirstChar', + })} @@ -532,7 +540,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { }} > {t('auth:action.copy_address', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} } @@ -577,7 +585,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { }} > {t('auth:action.copy_address', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} } diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index afcb686..e046ff1 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -98,7 +98,7 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { justifyContent: 'flex-start', }} > - {t('core:message.message', { postProcess: 'capitalizeFirst' })} + {t('core:message.message', { postProcess: 'capitalizeFirstChar' })} {/* Option 2: Send QORT */} @@ -117,7 +117,7 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { }} > {t('core:action.send_qort', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} @@ -133,7 +133,7 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { }} > {t('auth:action.copy_address', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} @@ -151,7 +151,7 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { }} > {t('core:user_lookup', { - postProcess: 'capitalizeFirst', + postProcess: 'capitalizeFirstChar', })} @@ -208,8 +208,8 @@ const BlockUser = ({ address, name, handleClose }) => { )} {isAlreadyBlocked && - t('core:action.unblock_name', { postProcess: 'capitalizeFirst' })} - {isAlreadyBlocked === false && t('core:action.block_name', { postProcess: 'capitalizeFirst' })}} + t('core:action.unblock_name', { postProcess: 'capitalizeFirstChar' })} + {isAlreadyBlocked === false && t('core:action.block_name', { postProcess: 'capitalizeFirstChar' })}} ); }; diff --git a/src/i18n/locales/en/auth.json b/src/i18n/locales/en/auth.json index a1dca21..ba017dd 100644 --- a/src/i18n/locales/en/auth.json +++ b/src/i18n/locales/en/auth.json @@ -35,6 +35,7 @@ "key": "API key", "select_valid": "select a valid apikey" }, + "blocked_users": "blocked users", "build_version": "build version", "message": { "error": { @@ -46,6 +47,7 @@ "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_reencrypt_secret_key": "unable to re-encrypt secret key" }, From eb857222936a0989a1f12ecc9ac64f62dd92ff20 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Tue, 20 May 2025 08:24:25 +0200 Subject: [PATCH 08/13] Add python script --- .gitignore | 5 ++- scripts/i18n-checker.py | 89 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 scripts/i18n-checker.py diff --git a/.gitignore b/.gitignore index 979a042..756e09b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ dist-ssr *.sln *.sw? release-builds/ -.env \ No newline at end of file +.env + +# reports from scripts +scripts/i18n_report* \ No newline at end of file diff --git a/scripts/i18n-checker.py b/scripts/i18n-checker.py new file mode 100644 index 0000000..604d222 --- /dev/null +++ b/scripts/i18n-checker.py @@ -0,0 +1,89 @@ +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'(?\s*([A-Z][a-z].*?)\s*<') + +def is_excluded(path): + return any(excluded in path for excluded in EXCLUDED_DIRS) + +def find_untranslated_strings(file_path): + issues = [] + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Match suspicious string literals + for match in STRING_LITERAL_REGEX.finditer(content): + string = match.group(1) + if not any(fn + '(' in content[match.start()-10:match.start()] for fn in I18N_FUNCTIONS): + issues.append({ + 'file': file_path, + 'position': match.start(), + 'type': 'StringLiteral', + 'text': string.strip() + }) + + # Match JSX text nodes + for match in JSX_TEXT_REGEX.finditer(content): + text = match.group(1) + if not text.strip().startswith('{t('): # naive check + issues.append({ + 'file': file_path, + 'position': match.start(), + 'type': 'JSXText', + 'text': text.strip() + }) + + 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', 'position', '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() From 9d25249b6d81d888429c618253bb7818e338188b Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Tue, 20 May 2025 08:35:11 +0200 Subject: [PATCH 09/13] Add translations --- src/components/Group/BlockedUsersModal.tsx | 55 ++++++++++++++----- .../Group/ListOfGroupPromotions.tsx | 2 + src/components/WrapperUserAction.tsx | 4 +- src/i18n/locales/en/auth.json | 15 ++++- src/i18n/locales/en/core.json | 2 - 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index de9ec80..5ebd2fc 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -200,20 +200,28 @@ export const BlockedUsersModal = () => { variant="contained" onClick={blockUser} > - Block + {t('auth:action.block', { postProcess: 'capitalizeFirstChar' })} {Object.entries(blockedUsers?.addresses).length > 0 && ( <> + - Blocked addresses- blocks processing of txs + {t('auth:message.generic.blocked_addresses', { + postProcess: 'capitalizeFirstChar', + })} + + + )} @@ -255,19 +263,26 @@ export const BlockedUsersModal = () => { } }} > - Unblock + {t('auth:action.unblock', { + postProcess: 'capitalizeFirstChar', + })} ); } )} + {Object.entries(blockedUsers?.names).length > 0 && ( <> + - Blocked names for QDN + {t('core:message.generic.blocked_names', { + postProcess: 'capitalizeFirstChar', + })} + )} @@ -291,6 +306,7 @@ export const BlockedUsersModal = () => { }} > {key} + ); })} + @@ -345,13 +364,19 @@ export const BlockedUsersModal = () => { aria-describedby="alert-dialog-description" > - {'Decide what to block'} + {t('auth:message.generic.decide_block', { + postProcess: 'capitalizeFirstChar', + })} - Blocking {message?.userName || message?.userAddress} + {t('auth:message.generic.blocking', { + name: message?.userName || message?.userAddress, + postProcess: 'capitalizeFirstChar', + })} + { }} />{' '} - Choose "block txs" or "all" to block chat messages{' '} + {t('auth:message.generic.choose_block', { + postProcess: 'capitalizeFirstChar', + })} @@ -378,7 +405,7 @@ export const BlockedUsersModal = () => { onOk('address'); }} > - Block txs + {t('auth:action.block_txs', { postProcess: 'capitalizeFirstChar' })} diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index a133c30..b2f22fb 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -269,6 +269,7 @@ export const ListOfGroupPromotions = () => { try { const groupId = group.groupId; const fee = await getFee('JOIN_GROUP'); + await show({ message: t('core:message.question.perform_transaction', { action: 'JOIN_GROUP', @@ -276,6 +277,7 @@ export const ListOfGroupPromotions = () => { }), publishFee: fee.fee + ' QORT', }); + setIsLoadingJoinGroup(true); await new Promise((res, rej) => { window diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index e046ff1..f3f21ee 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -208,8 +208,8 @@ const BlockUser = ({ address, name, handleClose }) => { )} {isAlreadyBlocked && - t('core:action.unblock_name', { postProcess: 'capitalizeFirstChar' })} - {isAlreadyBlocked === false && t('core:action.block_name', { postProcess: 'capitalizeFirstChar' })}} + t('auth:action.unblock_name', { postProcess: 'capitalizeFirstChar' })} + {isAlreadyBlocked === false && t('auth:action.block_name', { postProcess: 'capitalizeFirstChar' })}} ); }; diff --git a/src/i18n/locales/en/auth.json b/src/i18n/locales/en/auth.json index ba017dd..81e14a5 100644 --- a/src/i18n/locales/en/auth.json +++ b/src/i18n/locales/en/auth.json @@ -11,6 +11,12 @@ "seed_phrase": "add seed-phrase" }, "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_qortal_account": "create your Qortal account by clicking NEXT below.", @@ -22,7 +28,9 @@ "publish_group_secret_key": "publish group secret key", "reencrypt_key": "re-encrypt key", "return_to_list": "return to list", - "setup_qortal_account": "set up your Qortal account" + "setup_qortal_account": "set up your Qortal account", + "unblock": "unblock", + "unblock_name": "unblock name" }, "address": "address", "address_name": "address or name", @@ -52,7 +60,12 @@ "unable_reencrypt_secret_key": "unable to re-encrypt secret key" }, "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, you’re all set up!", + "decide_block": "decide what to block", "no_account": "no accounts saved", "no_minimum_length": "there is no minimum length requirement", "no_secret_key_published": "no secret key published yet", diff --git a/src/i18n/locales/en/core.json b/src/i18n/locales/en/core.json index 1d723f5..2a63179 100644 --- a/src/i18n/locales/en/core.json +++ b/src/i18n/locales/en/core.json @@ -8,7 +8,6 @@ "access_app": "access app", "backup_account": "backup account", "backup_wallet": "backup wallet", - "block_name": "block name", "cancel": "cancel", "cancel_invitation": "cancel invitation", "change": "change", @@ -78,7 +77,6 @@ "start_minting": "start minting", "start_typing": "start typing here...", "transfer_qort": "Transfer QORT", - "unblock_name": "unblock name", "unpin": "unpin", "unpin_app": "unpin app", "unpin_from_dashboard": "unpin from dashboard", From 33f758bbbc5d9de221ae1bff96257eb0cf5ec81b Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Tue, 20 May 2025 09:02:15 +0200 Subject: [PATCH 10/13] Add translations --- scripts/i18n-checker.py | 60 +++++++++++++++++++----------- src/hooks/useHandlePrivateApps.tsx | 20 +++++++--- src/i18n/locales/en/core.json | 3 ++ 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/scripts/i18n-checker.py b/scripts/i18n-checker.py index 604d222..12bd976 100644 --- a/scripts/i18n-checker.py +++ b/scripts/i18n-checker.py @@ -16,35 +16,53 @@ 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 'action' in text + ) + +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() - # Match suspicious string literals - for match in STRING_LITERAL_REGEX.finditer(content): - string = match.group(1) - if not any(fn + '(' in content[match.start()-10:match.start()] for fn in I18N_FUNCTIONS): - issues.append({ - 'file': file_path, - 'position': match.start(), - 'type': 'StringLiteral', - 'text': string.strip() - }) + 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 JSX text nodes - for match in JSX_TEXT_REGEX.finditer(content): - text = match.group(1) - if not text.strip().startswith('{t('): # naive check - issues.append({ - 'file': file_path, - 'position': match.start(), - 'type': 'JSXText', - 'text': text.strip() - }) + # 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): @@ -64,7 +82,7 @@ def save_report(results, output_file): 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', 'position', 'type', 'text']) + writer = csv.DictWriter(f, fieldnames=['file', 'line', 'type', 'text']) writer.writeheader() for row in results: writer.writerow(row) diff --git a/src/hooks/useHandlePrivateApps.tsx b/src/hooks/useHandlePrivateApps.tsx index be79d41..4e694c0 100644 --- a/src/hooks/useHandlePrivateApps.tsx +++ b/src/hooks/useHandlePrivateApps.tsx @@ -10,6 +10,7 @@ import { saveToLocalStorage } from '../components/Apps/AppsNavBarDesktop'; import { base64ToUint8Array } from '../qdn/encryption/group-encryption'; import { uint8ArrayToObject } from '../backgroundFunctions/encryption'; import { useSetAtom } from 'jotai'; +import { useTranslation } from 'react-i18next'; export const useHandlePrivateApps = () => { const [status, setStatus] = useState(''); @@ -20,8 +21,8 @@ export const useHandlePrivateApps = () => { setInfoSnackCustom, } = useContext(MyContext); const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom); - const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom); + const { t } = useTranslation(['auth', 'core', 'group']); const openApp = async ( privateAppProperties, @@ -29,7 +30,7 @@ export const useHandlePrivateApps = () => { setLoadingStatePrivateApp ) => { try { - if (setLoadingStatePrivateApp) { + if (setLoadingStatePrivateApp) { // TODO translate setLoadingStatePrivateApp(`Downloading and decrypting private app.`); } setOpenSnackGlobal(true); @@ -173,7 +174,7 @@ export const useHandlePrivateApps = () => { }); setInfoSnackCustom({ type: 'success', - message: 'Opened', + message: {t('core:message.generic.opened', { postProcess: 'capitalizeFirstChar' })}, }); if (setLoadingStatePrivateApp) { setLoadingStatePrivateApp(``); @@ -206,7 +207,12 @@ export const useHandlePrivateApps = () => { } catch (error) { if (setLoadingStatePrivateApp) { setLoadingStatePrivateApp( - `Error! ${error?.message || 'Unable to build private app.'}` + `Error! ${ + error?.message || + t('core:message.error.unable_build_app', { + postProcess: 'capitalizeFirstChar', + }) + }` ); } throw error; @@ -214,7 +220,11 @@ export const useHandlePrivateApps = () => { } catch (error) { setInfoSnackCustom({ type: 'error', - message: error?.message || 'Unable to fetch app', + message: + error?.message || + t('core:message.error.unable_fetch_app', { + postProcess: 'capitalizeFirstChar', + }), }); } }; diff --git a/src/i18n/locales/en/core.json b/src/i18n/locales/en/core.json index 2a63179..179cf85 100644 --- a/src/i18n/locales/en/core.json +++ b/src/i18n/locales/en/core.json @@ -172,8 +172,10 @@ "rating_option": "cannot find rating option", "save_qdn": "unable to save to QDN", "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_encrypt_app": "unable to encrypt app. App not published'", + "unable_fetch_app": "unable to build private app", "unable_publish_app": "unable to publish app", "unable_publish_image": "unable to publish image", "unable_rate": "unable to rate", @@ -215,6 +217,7 @@ "no_pinned_changes": "you currently do not have any changes to your pinned apps", "no_results": "no results", "one_app_per_name": "note: Currently, only one App and Website is allowed per Name.", + "opened": "opened", "overwrite_qdn": "overwrite to QDN", "password_confirm": "please confirm a password", "password_enter": "please enter a password", From 33829824658858bfc075bbe54796422768eae386 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Tue, 20 May 2025 09:07:24 +0200 Subject: [PATCH 11/13] Sort lists --- src/hooks/useQortalMessageListener.tsx | 252 ++++++++++++------------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/src/hooks/useQortalMessageListener.tsx b/src/hooks/useQortalMessageListener.tsx index a297e63..ec89437 100644 --- a/src/hooks/useQortalMessageListener.tsx +++ b/src/hooks/useQortalMessageListener.tsx @@ -168,157 +168,157 @@ export function openIndexedDB() { } 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', - 'REMOVE_FOREIGN_SERVER', - 'GET_DAY_SUMMARY', + 'ADD_GROUP_ADMIN', + '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_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_AESGCM', 'DECRYPT_DATA_WITH_SHARING_KEY', + 'DECRYPT_DATA', + 'DECRYPT_QORTAL_GROUP_DATA', 'DELETE_HOSTED_DATA', - 'GET_HOSTED_DATA', - 'PUBLISH_MULTIPLE_QDN_RESOURCES', - 'PUBLISH_QDN_RESOURCE', - 'ENCRYPT_DATA', + 'DELETE_LIST_ITEM', + 'DEPLOY_AT', 'ENCRYPT_DATA_WITH_SHARING_KEY', + 'ENCRYPT_DATA', 'ENCRYPT_QORTAL_GROUP_DATA', - 'SAVE_FILE', + 'FETCH_BLOCK_RANGE', + 'FETCH_BLOCK', + 'FETCH_QDN_RESOURCE', 'GET_ACCOUNT_DATA', 'GET_ACCOUNT_NAMES', - 'SEARCH_NAMES', - '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_ARRR_SYNC_STATUS', 'GET_AT_DATA', - 'LIST_ATS', - 'FETCH_BLOCK', - 'FETCH_BLOCK_RANGE', - 'SEARCH_TRANSACTIONS', - 'GET_PRICE', - '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', - 'CANCEL_GROUP_INVITE', - 'CREATE_GROUP', - 'GET_USER_WALLET_TRANSACTIONS', + 'GET_AT', + 'GET_BALANCE', + 'GET_CROSSCHAIN_SERVER_INFO', + 'GET_DAY_SUMMARY', + 'GET_FOREIGN_FEE', + 'GET_HOSTED_DATA', + 'GET_LIST_ITEMS', + 'GET_NAME_DATA', 'GET_NODE_INFO', 'GET_NODE_STATUS', - 'GET_ARRR_SYNC_STATUS', - 'SHOW_PDF_READER', - 'UPDATE_GROUP', - 'SELL_NAME', - 'CANCEL_SELL_NAME', - 'BUY_NAME', - 'SIGN_FOREIGN_FEES', + 'GET_PRICE', + 'GET_QDN_RESOURCE_METADATA', + 'GET_QDN_RESOURCE_PROPERTIES', + 'GET_QDN_RESOURCE_STATUS', + 'GET_QDN_RESOURCE_URL', + 'GET_SERVER_CONNECTION_HISTORY', + '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', + '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', + 'UPDATE_FOREIGN_FEE', + 'UPDATE_GROUP', + 'UPDATE_NAME', + 'VOTE_ON_POLL', ]; 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', - 'REMOVE_FOREIGN_SERVER', - 'GET_DAY_SUMMARY', + 'ADD_GROUP_ADMIN', + '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_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', - 'CANCEL_GROUP_INVITE', - 'CREATE_GROUP', - 'GET_USER_WALLET_TRANSACTIONS', + 'DECRYPT_DATA_WITH_SHARING_KEY', + 'DECRYPT_DATA', + '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_STATUS', - 'GET_ARRR_SYNC_STATUS', - 'SHOW_PDF_READER', - 'UPDATE_GROUP', - 'SELL_NAME', - 'CANCEL_SELL_NAME', - 'BUY_NAME', - 'SIGN_FOREIGN_FEES', + 'GET_SERVER_CONNECTION_HISTORY', + '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', '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', + 'UPDATE_FOREIGN_FEE', + 'UPDATE_GROUP', + 'UPDATE_NAME', + 'VOTE_ON_POLL', ]; async function retrieveFileFromIndexedDB(fileId) { From bd1a7b5a5ab79df7ee081814837366e12c3ea5a9 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Tue, 20 May 2025 09:14:10 +0200 Subject: [PATCH 12/13] Add comment --- scripts/i18n-checker.py | 3 ++- src/hooks/useQortalMessageListener.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/i18n-checker.py b/scripts/i18n-checker.py index 12bd976..45cfaa2 100644 --- a/scripts/i18n-checker.py +++ b/scripts/i18n-checker.py @@ -18,7 +18,8 @@ def is_excluded(path): def is_ignorable(text): return ( - re.fullmatch(r'[A-Z0-9_]+', text) and 'action' in text + re.fullmatch(r'[A-Z0-9_]+', text) and + any(keyword in text.lower() for keyword in ['action', 'status']) ) def is_console_log_line(line): diff --git a/src/hooks/useQortalMessageListener.tsx b/src/hooks/useQortalMessageListener.tsx index ec89437..3105bdc 100644 --- a/src/hooks/useQortalMessageListener.tsx +++ b/src/hooks/useQortalMessageListener.tsx @@ -165,7 +165,7 @@ export function openIndexedDB() { reject('Error opening IndexedDB'); }; }); -} +} // TODO translate export const listOfAllQortalRequests = [ 'ADD_FOREIGN_SERVER', From 4a71cc3f30110e24e3351d574902ec2df6fd758d Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Tue, 20 May 2025 19:43:29 +0200 Subject: [PATCH 13/13] Add translations and new items --- src/components/Embeds/VideoPlayer.tsx | 2 +- src/components/Group/GroupJoinRequests.tsx | 25 ++++--- .../Group/ListOfThreadPostsWatched.tsx | 7 +- src/components/QortPayment.tsx | 64 ++++++++++------ src/hooks/useHandlePrivateApps.tsx | 74 ++++++++++++++----- src/hooks/useQortalMessageListener.tsx | 4 +- src/i18n/locales/en/auth.json | 3 + src/i18n/locales/en/core.json | 15 +++- src/i18n/locales/en/group.json | 1 + 9 files changed, 136 insertions(+), 59 deletions(-) diff --git a/src/components/Embeds/VideoPlayer.tsx b/src/components/Embeds/VideoPlayer.tsx index 7bb1272..5bcf181 100644 --- a/src/components/Embeds/VideoPlayer.tsx +++ b/src/components/Embeds/VideoPlayer.tsx @@ -67,7 +67,7 @@ interface VideoPlayerProps { user?: string; } -// TODO translate and theme? Is it worth? +// TODO translate and theme (optional) export const VideoPlayer: FC = ({ poster, name, diff --git a/src/components/Group/GroupJoinRequests.tsx b/src/components/Group/GroupJoinRequests.tsx index b9bffc9..8c2f097 100644 --- a/src/components/Group/GroupJoinRequests.tsx +++ b/src/components/Group/GroupJoinRequests.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useEffect, useMemo, useState } from 'react'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; @@ -27,12 +27,10 @@ export const GroupJoinRequests = ({ setMobileViewMode, setDesktopViewMode, }) => { - const [isExpanded, setIsExpanded] = React.useState(false); + const [isExpanded, setIsExpanded] = useState(false); const { t } = useTranslation(['auth', 'core', 'group']); - const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState( - [] - ); - const [loading, setLoading] = React.useState(true); + const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]); + const [loading, setLoading] = useState(true); const [txList] = useAtom(txListAtom); const setMyGroupsWhereIAmAdmin = useSetAtom(myGroupsWhereIAmAdminAtom); @@ -91,7 +89,7 @@ export const GroupJoinRequests = ({ } }; - React.useEffect(() => { + useEffect(() => { if (myAddress && groups.length > 0) { getJoinRequests(); } else { @@ -99,7 +97,7 @@ export const GroupJoinRequests = ({ } }, [myAddress, groups]); - const filteredJoinRequests = React.useMemo(() => { + const filteredJoinRequests = useMemo(() => { return groupsWithJoinRequests.map((group) => { const filteredGroupRequests = group?.data?.filter((gd) => { const findJoinRequsetInTxList = txList?.find( @@ -271,8 +269,15 @@ export const GroupJoinRequests = ({ fontSize: '13px', fontWeight: 400, }, - }} // TODO translate - 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', + } + )} /> diff --git a/src/components/Group/ListOfThreadPostsWatched.tsx b/src/components/Group/ListOfThreadPostsWatched.tsx index 024612f..e0c4820 100644 --- a/src/components/Group/ListOfThreadPostsWatched.tsx +++ b/src/components/Group/ListOfThreadPostsWatched.tsx @@ -174,8 +174,11 @@ export const ListOfThreadPostsWatched = () => { } > - diff --git a/src/components/QortPayment.tsx b/src/components/QortPayment.tsx index 48074c6..c031914 100644 --- a/src/components/QortPayment.tsx +++ b/src/components/QortPayment.tsx @@ -1,15 +1,7 @@ -import { Box, CircularProgress, useTheme } from '@mui/material'; +import { Box, useTheme } from '@mui/material'; import { useState } from 'react'; -import { - CustomButton, - CustomInput, - CustomLabel, - TextP, -} from '../styles/App-styles'; +import { TextP } from '../styles/App-styles'; 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 { useTranslation } from 'react-i18next'; @@ -28,22 +20,37 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { setSendPaymentError(''); setSendPaymentSuccess(''); if (!paymentTo) { - setSendPaymentError('Please enter a recipient'); + setSendPaymentError( + t('auth:action.enter_recipient', { + postProcess: 'capitalizeFirstChar', + }) + ); return; } if (!paymentAmount) { - setSendPaymentError('Please enter an amount greater than 0'); + setSendPaymentError( + t('auth:action.enter_amount', { + postProcess: 'capitalizeFirstChar', + }) + ); return; } if (!paymentPassword) { - setSendPaymentError('Please enter your wallet password'); + setSendPaymentError( + t('auth:action.enter_wallet_password', { + postProcess: 'capitalizeFirstChar', + }) + ); return; } - const fee = await getFee('PAYMENT'); // TODO translate + const fee = await getFee('PAYMENT'); 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', }); @@ -89,7 +96,9 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { textAlign: 'start', }} > - Transfer QORT + {t('core:action.transfer_qort', { + postProcess: 'capitalizeFirstChar', + })} @@ -103,7 +112,9 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { textAlign: 'start', }} > - Balance: + {t('core:balance', { + postProcess: 'capitalizeFirstChar', + })} { - To + + {t('core:to', { + postProcess: 'capitalizeFirstChar', + })} + @@ -134,7 +149,11 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { - Amount + + {t('core:amount', { + postProcess: 'capitalizeFirstChar', + })} + @@ -151,7 +170,9 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { - Confirm wallet password + {t('auth:wallet.password_confirmation', { + postProcess: 'capitalizeFirstChar', + })} @@ -173,7 +194,6 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { {sendPaymentError} - {/* {sendPaymentSuccess} */} @@ -194,7 +214,7 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { }} /> )} - Send + {t('core:action.send', { postProcess: 'capitalizeFirstChar' })} ); diff --git a/src/hooks/useHandlePrivateApps.tsx b/src/hooks/useHandlePrivateApps.tsx index 4e694c0..1115b52 100644 --- a/src/hooks/useHandlePrivateApps.tsx +++ b/src/hooks/useHandlePrivateApps.tsx @@ -30,14 +30,20 @@ export const useHandlePrivateApps = () => { setLoadingStatePrivateApp ) => { try { - if (setLoadingStatePrivateApp) { // TODO translate - setLoadingStatePrivateApp(`Downloading and decrypting private app.`); + if (setLoadingStatePrivateApp) { + setLoadingStatePrivateApp( + t('core:message.generic.downloading_decrypting_app', { + postProcess: 'capitalizeFirstChar', + }) + ); } setOpenSnackGlobal(true); setInfoSnackCustom({ type: 'info', - message: 'Fetching app data', + message: t('core:message.generic.fetching_data', { + postProcess: 'capitalizeFirstChar', + }), duration: null, }); const urlData = `${getBaseApiReact()}/arbitrary/${ @@ -56,7 +62,11 @@ export const useHandlePrivateApps = () => { if (!responseData?.ok) { 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'); @@ -65,13 +75,25 @@ export const useHandlePrivateApps = () => { data = await responseData.text(); if (data?.error) { 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) { if (setLoadingStatePrivateApp) { - setLoadingStatePrivateApp('Error! Unable to download private app.'); + setLoadingStatePrivateApp( + t('core:message.generic.unable_download_private_app', { + postProcess: 'capitalizeFirstChar', + }) + ); } throw error; } @@ -79,23 +101,27 @@ export const useHandlePrivateApps = () => { let decryptedData; // eslint-disable-next-line no-useless-catch try { - decryptedData = await window.sendMessage( - 'DECRYPT_QORTAL_GROUP_DATA', - - { - base64: data, - groupId: privateAppProperties?.groupId, - } - ); + decryptedData = await window.sendMessage('DECRYPT_QORTAL_GROUP_DATA', { + base64: data, + groupId: privateAppProperties?.groupId, + }); if (decryptedData?.error) { if (setLoadingStatePrivateApp) { - setLoadingStatePrivateApp('Error! Unable to decrypt private app.'); + setLoadingStatePrivateApp( + t('core:message.generic.unable_decrypt_app', { + postProcess: 'capitalizeFirstChar', + }) + ); } throw new Error(decryptedData?.error); } } catch (error) { if (setLoadingStatePrivateApp) { - setLoadingStatePrivateApp('Error! Unable to decrypt private app.'); + setLoadingStatePrivateApp( + t('core:message.generic.unable_decrypt_app', { + postProcess: 'capitalizeFirstChar', + }) + ); } throw error; } @@ -107,11 +133,15 @@ export const useHandlePrivateApps = () => { if (decryptedData) { setInfoSnackCustom({ type: 'info', - message: 'Building app', + message: t('core:message.generic.building_app', { + postProcess: 'capitalizeFirstChar', + }), }); + const endpoint = await createEndpoint( `/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true` ); + const response = await fetch(endpoint, { method: 'POST', headers: { @@ -119,7 +149,9 @@ export const useHandlePrivateApps = () => { }, body: UintToObject?.app, }); + const previewPath = await response.text(); + const refreshfunc = async (tabId, privateAppProperties) => { const checkIfPreviewLinkStillWorksUrl = await createEndpoint( `/render/hash/HmtnZpcRPwisMfprUXuBp27N2xtv5cDiQjqGZo8tbZS?secret=E39WTiG4qBq3MFcMPeRZabtQuzyfHg9ZuR5SgY7nW1YH` @@ -133,6 +165,7 @@ export const useHandlePrivateApps = () => { const endpoint = await createEndpoint( `/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true` ); + const response = await fetch(endpoint, { method: 'POST', headers: { @@ -140,6 +173,7 @@ export const useHandlePrivateApps = () => { }, body: UintToObject?.app, }); + const previewPath = await response.text(); executeEvent('updateAppUrl', { tabId: tabId, @@ -174,7 +208,9 @@ export const useHandlePrivateApps = () => { }); setInfoSnackCustom({ type: 'success', - message: {t('core:message.generic.opened', { postProcess: 'capitalizeFirstChar' })}, + message: t('core:message.generic.opened', { + postProcess: 'capitalizeFirstChar', + }), }); if (setLoadingStatePrivateApp) { setLoadingStatePrivateApp(``); diff --git a/src/hooks/useQortalMessageListener.tsx b/src/hooks/useQortalMessageListener.tsx index 3105bdc..1a8670b 100644 --- a/src/hooks/useQortalMessageListener.tsx +++ b/src/hooks/useQortalMessageListener.tsx @@ -165,7 +165,7 @@ export function openIndexedDB() { reject('Error opening IndexedDB'); }; }); -} // TODO translate +} export const listOfAllQortalRequests = [ 'ADD_FOREIGN_SERVER', @@ -658,7 +658,7 @@ export const useQortalMessageListener = ( setPath(pathUrl); if (appName?.toLowerCase() === 'q-mail') { window.sendMessage('addEnteredQmailTimestamp').catch((error) => { - // error + // TODO print error }); } else if (appName?.toLowerCase() === 'q-wallets') { executeEvent('setLastEnteredTimestampPaymentEvent', {}); diff --git a/src/i18n/locales/en/auth.json b/src/i18n/locales/en/auth.json index 81e14a5..e091927 100644 --- a/src/i18n/locales/en/auth.json +++ b/src/i18n/locales/en/auth.json @@ -22,6 +22,9 @@ "create_qortal_account": "create your Qortal account by clicking NEXT below.", "choose_password": "choose new password", "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", "insert_name_address": "please insert a name or address", "publish_admin_secret_key": "publish admin secret key", diff --git a/src/i18n/locales/en/core.json b/src/i18n/locales/en/core.json index 179cf85..eaae575 100644 --- a/src/i18n/locales/en/core.json +++ b/src/i18n/locales/en/core.json @@ -70,6 +70,7 @@ "select_app_type": "select App Type", "select_category": "select Category", "select_name_app": "select Name/App", + "send": "send", "send_qort": "send QORT", "set_avatar": "set avatar", "show": "show", @@ -98,7 +99,7 @@ "apps_dashboard": "apps Dashboard", "apps_official": "official Apps", "attachment": "attachment", - "balance": "balance", + "balance": "balance:", "category": "category", "category_other": "categories", "chat": "chat", @@ -174,8 +175,10 @@ "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_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_fetch_app": "unable to build private app", + "unable_fetch_app": "unable to fetch app", "unable_publish_app": "unable to publish app", "unable_publish_image": "unable to publish image", "unable_rate": "unable to rate", @@ -186,16 +189,19 @@ "already_voted": "you've already voted.", "avatar_size": "{{ size }} KB max. for GIFS", "building": "building", + "building_app": "building app", "created_by": "created by {{ owner }}", "buy_order_request": "the Application
{{hostname}}
is requesting {{count}} buy order", "buy_order_request_other": "the Application
{{hostname}}
is requesting {{count}} buy orders", "devmode_local_node": "please use your local node for dev mode! Logout and use Local node.", "downloading": "downloading", + "downloading_decrypting_app": "downloading and decrypting private app.", "edited": "edited", "editing_message": "editing message", "encrypted": "encrypted", "encrypted_not": "not encrypted", "fee_qort": "fee: {{ message }} QORT", + "fetching_data": "fetching app data", "foreign_fee": "foreign fee: {{ message }}", "mentioned": "mentioned", "message_with_image": "this message already has an image", @@ -259,7 +265,8 @@ "rate_app": "would you like to rate this app a rating of {{ rate }}?. It will create a POLL tx.", "register_name": "would you like to register this name?", "reset_pinned": "don't like your current local changes? Would you like to reset to the default pinned apps?", - "reset_qdn": "don't like your current local changes? Would you like to reset to your saved QDN pinned apps?" + "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": { "minting": "(minting)", @@ -280,6 +287,7 @@ "minting_status": "minting status", "name": "name", "name_app": "name/App", + "new_post_in": "new post in {{ title }}", "none": "none", "option": "option", "option_other": "options", @@ -325,6 +333,7 @@ "time": "time" }, "title": "title", + "to": "to", "tutorial": "tutorial", "user_lookup": "user lookup", "vote": "vote", diff --git a/src/i18n/locales/en/group.json b/src/i18n/locales/en/group.json index c4042bf..c58e88c 100644 --- a/src/i18n/locales/en/group.json +++ b/src/i18n/locales/en/group.json @@ -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.", "only_encrypted": "only unencrypted messages will be displayed.", "only_private_groups": "only private groups will be shown", + "pending_join_requests": "{{ group }} has {{ count }} pending join requests", "private_key_copied": "private key copied", "provide_message": "please provide a first message to the thread", "secure_place": "keep your private key in a secure place. Do not share!",