From 1dd0154d31e50cf3f69d526f313f83d5853d30bb Mon Sep 17 00:00:00 2001 From: Jan Jaroszczak Date: Thu, 25 Jan 2024 16:32:31 +0100 Subject: [PATCH] feature/80-i18next-implementation --- src/vva-fe/package.json | 2 + .../src/components/atoms/CopyButton.tsx | 5 +- .../src/components/atoms/StakeRadio.tsx | 9 +- .../src/components/atoms/VotingPowerChips.tsx | 11 +- .../src/components/molecules/DRepInfoCard.tsx | 4 +- .../molecules/GovernanceActionCard.tsx | 27 +- .../molecules/GovernanceActionsFilters.tsx | 5 +- .../molecules/GovernanceActionsSorting.tsx | 7 +- .../molecules/GovernanceVotedOnCard.tsx | 31 +- .../components/molecules/VoteActionForm.tsx | 32 +- .../components/molecules/VotesSubmitted.tsx | 13 +- .../components/molecules/WalletInfoCard.tsx | 6 +- .../organisms/ChooseStakeKeyPanel.tsx | 10 +- .../organisms/ChooseWalletModal.tsx | 19 +- .../components/organisms/DashboardCards.tsx | 208 +++++----- .../DashboardGovernanceActionDetails.tsx | 14 +- .../organisms/DashboardGovernanceActions.tsx | 6 +- .../DashboardGovernanceActionsVotedOn.tsx | 7 +- .../components/organisms/DashboardTopNav.tsx | 10 +- .../organisms/DelegateTodRepStepOne.tsx | 67 +-- .../organisms/DelegateTodRepStepTwo.tsx | 14 +- .../src/components/organisms/Drawer.tsx | 6 +- .../src/components/organisms/DrawerMobile.tsx | 4 +- .../organisms/ExternalLinkModal.tsx | 18 +- .../src/components/organisms/Footer.tsx | 6 +- .../organisms/GovernanceActionDetailsCard.tsx | 25 +- .../organisms/GovernanceActionsToVote.tsx | 4 +- src/vva-fe/src/components/organisms/Hero.tsx | 19 +- .../src/components/organisms/HomeCards.tsx | 24 +- .../organisms/RegisterAsdRepStepOne.tsx | 19 +- .../organisms/RegisterAsdRepStepTwo.tsx | 22 +- .../src/components/organisms/Slider.tsx | 6 +- .../src/components/organisms/StatusModal.tsx | 6 +- .../src/components/organisms/TopNav.tsx | 6 +- .../components/organisms/VotingPowerModal.tsx | 11 +- src/vva-fe/src/consts/index.ts | 1 - src/vva-fe/src/consts/texts.ts | 42 -- src/vva-fe/src/context/snackbar.tsx | 4 +- src/vva-fe/src/context/wallet.tsx | 117 +++--- .../src/hooks/forms/useDelegateTodRepForm.tsx | 13 +- .../forms/useRegisterAsdRepFormContext.tsx | 13 +- .../hooks/forms/useUpdatedRepMetadataForm.tsx | 6 +- .../forms/useUrlAndHashFormController.tsx | 27 +- .../src/hooks/forms/useVoteActionForm.tsx | 27 +- .../DashboardGovernanceActionsCategory.tsx | 12 +- src/vva-fe/src/pages/DelegateTodRep.tsx | 4 +- src/vva-fe/src/pages/ErrorPage.tsx | 13 +- .../src/pages/GovernanceActionDetails.tsx | 16 +- src/vva-fe/src/pages/GovernanceActions.tsx | 4 +- .../src/pages/GovernanceActionsCategory.tsx | 16 +- src/vva-fe/src/pages/RegisterAsdRep.tsx | 4 +- src/vva-fe/src/pages/UpdatedRepMetadata.tsx | 19 +- src/vva-fe/src/translations/TypedTrans.tsx | 17 + src/vva-fe/src/translations/i18n.ts | 15 + src/vva-fe/src/translations/index.ts | 3 + src/vva-fe/src/translations/locales/en.ts | 382 ++++++++++++++++++ src/vva-fe/src/translations/types.ts | 26 ++ src/vva-fe/src/translations/usei18n.ts | 15 + src/vva-fe/tsconfig.json | 3 +- src/vva-fe/vite.config.ts | 4 + src/vva-fe/yarn.lock | 134 +++++- 61 files changed, 1134 insertions(+), 486 deletions(-) delete mode 100644 src/vva-fe/src/consts/texts.ts create mode 100644 src/vva-fe/src/translations/TypedTrans.tsx create mode 100644 src/vva-fe/src/translations/i18n.ts create mode 100644 src/vva-fe/src/translations/index.ts create mode 100644 src/vva-fe/src/translations/locales/en.ts create mode 100644 src/vva-fe/src/translations/types.ts create mode 100644 src/vva-fe/src/translations/usei18n.ts diff --git a/src/vva-fe/package.json b/src/vva-fe/package.json index 18dac1b01..404c6e327 100644 --- a/src/vva-fe/package.json +++ b/src/vva-fe/package.json @@ -28,11 +28,13 @@ "buffer": "^6.0.3", "date-fns": "^2.30.0", "esbuild": "^0.19.8", + "i18next": "^23.7.19", "keen-slider": "^6.8.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-gtm-module": "^2.0.11", "react-hook-form": "^7.47.0", + "react-i18next": "^14.0.1", "react-query": "^3.39.3", "react-router-dom": "^6.13.0", "storybook-addon-manual-mocks": "^1.0.3", diff --git a/src/vva-fe/src/components/atoms/CopyButton.tsx b/src/vva-fe/src/components/atoms/CopyButton.tsx index dc4eda8b9..f2277a5ec 100644 --- a/src/vva-fe/src/components/atoms/CopyButton.tsx +++ b/src/vva-fe/src/components/atoms/CopyButton.tsx @@ -2,6 +2,7 @@ import { useMemo } from "react"; import { ICONS } from "@consts"; import { useSnackbar } from "@context"; +import { usei18n } from "@translations"; interface Props { isChecked?: boolean; @@ -11,6 +12,8 @@ interface Props { export const CopyButton = ({ isChecked, text, variant }: Props) => { const { addSuccessAlert } = useSnackbar(); + const { t } = usei18n(); + const iconSrc = useMemo(() => { if (variant === "blue") { return ICONS.copyBlueIcon; @@ -29,7 +32,7 @@ export const CopyButton = ({ isChecked, text, variant }: Props) => { alt="copy" onClick={(e) => { navigator.clipboard.writeText(text); - addSuccessAlert("Copied to clipboard."); + addSuccessAlert(t("alerts.copiedToClipboard")); e.stopPropagation(); }} src={iconSrc} diff --git a/src/vva-fe/src/components/atoms/StakeRadio.tsx b/src/vva-fe/src/components/atoms/StakeRadio.tsx index cb846b68d..b889c44ae 100644 --- a/src/vva-fe/src/components/atoms/StakeRadio.tsx +++ b/src/vva-fe/src/components/atoms/StakeRadio.tsx @@ -3,7 +3,8 @@ import { Box, IconButton, Typography } from "@mui/material"; import { ICONS } from "@consts"; import { theme } from "@/theme"; -import { useGetAdaHolderVotingPowerQuery, useScreenDimension } from "@/hooks"; +import { useGetAdaHolderVotingPowerQuery, useScreenDimension } from "@hooks"; +import { usei18n } from "@translations"; import { correctAdaFormat } from "@/utils/adaFormat"; type StakeRadioProps = { @@ -21,6 +22,7 @@ export const StakeRadio: FC = ({ ...props }) => { const { isMobile } = useScreenDimension(); const { powerIsLoading, votingPower } = useGetAdaHolderVotingPowerQuery(stakeKey); + const { t } = usei18n(); return ( = ({ ...props }) => { - Voting power: + {t("votingPower")} {powerIsLoading ? ( - {" "} - Loading... + {t("loading")} ) : ( { const { dRep, stakeKey, isDrepLoading } = useCardano(); @@ -19,6 +19,7 @@ export const VotingPowerChips = () => { const { votingPower, powerIsLoading } = useGetAdaHolderVotingPowerQuery(stakeKey); const { isMobile, screenWidth } = useScreenDimension(); + const { t } = usei18n(); return ( { > {dRep?.isRegistered && ( @@ -51,7 +52,7 @@ export const VotingPowerChips = () => { )} {screenWidth >= 1024 && ( - Voting power: + {t("votingPower")} )} {(dRep?.isRegistered && drepPowerIsLoading) || diff --git a/src/vva-fe/src/components/molecules/DRepInfoCard.tsx b/src/vva-fe/src/components/molecules/DRepInfoCard.tsx index 83d49f480..40b5a4895 100644 --- a/src/vva-fe/src/components/molecules/DRepInfoCard.tsx +++ b/src/vva-fe/src/components/molecules/DRepInfoCard.tsx @@ -2,15 +2,17 @@ import { Box, Typography } from "@mui/material"; import { useCardano } from "@context"; import { CopyButton } from "@atoms"; +import { usei18n } from "@translations"; export const DRepInfoCard = () => { const { dRepIDBech32 } = useCardano(); + const { t } = usei18n(); return ( - My DRep ID: + {t("myDRepId")} diff --git a/src/vva-fe/src/components/molecules/GovernanceActionCard.tsx b/src/vva-fe/src/components/molecules/GovernanceActionCard.tsx index 73a50af91..ab0c7a345 100644 --- a/src/vva-fe/src/components/molecules/GovernanceActionCard.tsx +++ b/src/vva-fe/src/components/molecules/GovernanceActionCard.tsx @@ -3,7 +3,6 @@ import { Box } from "@mui/material"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Button, Typography, Tooltip } from "@atoms"; -import { tooltips } from "@consts"; import { useScreenDimension } from "@hooks"; import { theme } from "@/theme"; import { @@ -12,6 +11,7 @@ import { getProposalTypeLabel, getShortenedGovActionId, } from "@utils"; +import { usei18n } from "@translations"; interface ActionTypeProps extends Omit< @@ -43,6 +43,7 @@ export const GovernanceActionCard: FC = ({ ...props }) => { index, } = props; const { isMobile, screenWidth } = useScreenDimension(); + const { t } = usei18n(); const { palette: { lightBlue }, @@ -85,7 +86,7 @@ export const GovernanceActionCard: FC = ({ ...props }) => { }} > - In progress + {t("inProgress")} )} @@ -101,7 +102,7 @@ export const GovernanceActionCard: FC = ({ ...props }) => { > - Governance Action Type: + {t("govActions.type")} = ({ ...props }) => { - Governance Action ID: + {t("govActions.id")} = ({ ...props }) => { sx={{ flexWrap: "nowrap", mr: 1 }} variant="caption" > - Submission date: + {t("govActions.submissionDate")} = ({ ...props }) => { {formatDisplayDate(createdDate)} @@ -196,7 +197,7 @@ export const GovernanceActionCard: FC = ({ ...props }) => { sx={{ flexWrap: "nowrap", mr: 1 }} variant="caption" > - Expiry date: + {t("govActions.expiryDate")} = ({ ...props }) => { {formatDisplayDate(expiryDate)} @@ -237,7 +238,9 @@ export const GovernanceActionCard: FC = ({ ...props }) => { }} data-testid={`govaction-${govActionId}-view-detail`} > - {inProgress ? "See transaction" : "View proposal details"} + {inProgress + ? t("seeTransaction") + : t("govActions.viewProposalDetails")} diff --git a/src/vva-fe/src/components/molecules/GovernanceActionsFilters.tsx b/src/vva-fe/src/components/molecules/GovernanceActionsFilters.tsx index 0a7426b90..3e81ff5c3 100644 --- a/src/vva-fe/src/components/molecules/GovernanceActionsFilters.tsx +++ b/src/vva-fe/src/components/molecules/GovernanceActionsFilters.tsx @@ -8,6 +8,7 @@ import { } from "@mui/material"; import { GOVERNANCE_ACTIONS_FILTERS } from "@consts"; +import { usei18n } from "@translations"; interface Props { chosenFilters: string[]; @@ -32,6 +33,8 @@ export const GovernanceActionsFilters = ({ [chosenFilters, setChosenFilters] ); + const { t } = usei18n(); + return ( - Governance Action Type + {t("govActions.filterTitle")} {GOVERNANCE_ACTIONS_FILTERS.map((item) => { return ( diff --git a/src/vva-fe/src/components/molecules/GovernanceActionsSorting.tsx b/src/vva-fe/src/components/molecules/GovernanceActionsSorting.tsx index 9abce9ae3..03790d6d4 100644 --- a/src/vva-fe/src/components/molecules/GovernanceActionsSorting.tsx +++ b/src/vva-fe/src/components/molecules/GovernanceActionsSorting.tsx @@ -9,6 +9,7 @@ import { } from "@mui/material"; import { GOVERNANCE_ACTIONS_SORTING } from "@consts"; +import { usei18n } from "@translations"; interface Props { chosenSorting: string; @@ -19,6 +20,8 @@ export const GovernanceActionsSorting = ({ chosenSorting, setChosenSorting, }: Props) => { + const { t } = usei18n(); + return ( - Sort by + {t("sortBy")} setChosenSorting("")}> - Clear + {t("clear")} diff --git a/src/vva-fe/src/components/molecules/GovernanceVotedOnCard.tsx b/src/vva-fe/src/components/molecules/GovernanceVotedOnCard.tsx index 855281658..eae4d2254 100644 --- a/src/vva-fe/src/components/molecules/GovernanceVotedOnCard.tsx +++ b/src/vva-fe/src/components/molecules/GovernanceVotedOnCard.tsx @@ -15,8 +15,8 @@ import { getShortenedGovActionId, openInNewTab, } from "@utils"; +import { usei18n } from "@translations"; import { Tooltip } from "@atoms"; -import { tooltips } from "@/consts/texts"; interface Props { votedProposal: VotedProposal; @@ -31,6 +31,7 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { palette: { lightBlue }, } = theme; const { isMobile } = useScreenDimension(); + const { t } = usei18n(); const proposalTypeNoEmptySpaces = getProposalTypeLabel(proposal.type).replace( / /g, @@ -77,11 +78,11 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { variant="body2" > {inProgress ? ( - "In progress" + t("inProgress") ) : ( <> - Vote submitted + {t("govActions.voteSubmitted")} )} @@ -98,7 +99,7 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { > - Governance Action Type: + {t("govActions.type")} { - Governance Action ID: + {t("govActions.id")} { - My Vote: + {t("govActions.myVote")} { whiteSpace: "nowrap", }} > - Vote transaction + {t("govActions.voteTransaction")} @@ -187,14 +188,14 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { py={0.75} > - Submission date: + {t("govActions.submissionDate")} {formatDisplayDate(proposal.createdDate)} @@ -219,15 +220,15 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { py={0.75} > - Expiry date: + {t("govActions.expiryDate")} {formatDisplayDate(proposal.expiryDate)} @@ -273,7 +274,7 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { }} variant="outlined" > - Change your vote + {t("govActions.changeYourVote")} diff --git a/src/vva-fe/src/components/molecules/VoteActionForm.tsx b/src/vva-fe/src/components/molecules/VoteActionForm.tsx index 591bf39ec..3a98fa105 100644 --- a/src/vva-fe/src/components/molecules/VoteActionForm.tsx +++ b/src/vva-fe/src/components/molecules/VoteActionForm.tsx @@ -7,6 +7,7 @@ import { ICONS } from "@consts"; import { useCardano, useModal } from "@context"; import { useScreenDimension, useVoteActionForm } from "@hooks"; import { openInNewTab } from "@utils"; +import { usei18n } from "@translations"; export const VoteActionForm = ({ voteFromEP, @@ -24,6 +25,7 @@ export const VoteActionForm = ({ const { isMobile, screenWidth } = useScreenDimension(); const { openModal } = useModal(); const { dRep } = useCardano(); + const { t } = usei18n(); const { setValue, @@ -65,7 +67,7 @@ export const VoteActionForm = ({ width: "100%", }} > - Cancel + {t("cancel")} ); }, [state]); @@ -89,7 +91,7 @@ export const VoteActionForm = ({ height: 48, }} > - Change vote + {t("govActions.changeVote")} ); }, [confirmVote, areFormErrors, vote]); @@ -102,7 +104,9 @@ export const VoteActionForm = ({ flexDirection="column" px={screenWidth < 1024 ? 0 : 5} > - Choose how you want to vote: + + {t("govActions.chooseHowToVote")} + @@ -121,7 +125,7 @@ export const VoteActionForm = ({ name="vote" register={registerInput} setValue={setValue} - title="No" + title={t("no")} value="no" /> @@ -132,7 +136,7 @@ export const VoteActionForm = ({ name="vote" register={registerInput} setValue={setValue} - title="Abstain" + title={t("abstain")} value="abstain" /> @@ -155,7 +159,7 @@ export const VoteActionForm = ({ }); }} > - Show votes + {t("govActions.showVotes")} )} - Provide context about your vote{" "} + {t("govActions.provideContext")}{" "} - (optional) + {t("govActions.optional")} arrow - How to create URL and hash? + {t("forms.howCreateUrlAndHash")} @@ -239,7 +243,7 @@ export const VoteActionForm = ({ }} variant="caption" > - Select a different option to change your vote + {t("govActions.selectDifferentOption")} {(state?.vote && state?.vote !== vote) || (voteFromEP && voteFromEP !== vote) ? ( @@ -268,7 +272,7 @@ export const VoteActionForm = ({ width: "100%", }} > - Vote + {t("govActions.vote")} )} diff --git a/src/vva-fe/src/components/molecules/VotesSubmitted.tsx b/src/vva-fe/src/components/molecules/VotesSubmitted.tsx index dc7b9a199..d045ca4c2 100644 --- a/src/vva-fe/src/components/molecules/VotesSubmitted.tsx +++ b/src/vva-fe/src/components/molecules/VotesSubmitted.tsx @@ -3,7 +3,8 @@ import { Box, Typography } from "@mui/material"; import { theme } from "@/theme"; import { VotePill } from "@atoms"; -import { useScreenDimension } from "@/hooks"; +import { useScreenDimension } from "@hooks"; +import { usei18n } from "@translations"; import { correctAdaFormat } from "@/utils/adaFormat"; interface Props { @@ -17,6 +18,7 @@ export const VotesSubmitted = ({ yesVotes, noVotes, abstainVotes }: Props) => { palette: { lightBlue }, } = theme; const { isMobile } = useScreenDimension(); + const { t } = usei18n(); return ( { style={{ marginBottom: "10px" }} /> - Votes Submitted + {t("govActions.voteSubmitted")} - for this Governance Action + {t("govActions.forGovAction")} - Votes submitted on-chain by DReps, SPOs and Constitutional Committee - members. + {t("govActions.votesSubmittedOnChain")} { marginTop: "40px", }} > - Votes: + {t("govActions.votes")} { const { address, disconnectWallet, isMainnet } = useCardano(); const navigate = useNavigate(); + const { t } = usei18n(); const { palette: { lightBlue }, } = theme; @@ -38,7 +40,7 @@ export const WalletInfoCard = () => { - Connected Wallet: + {t("wallet.connectedWallet")} { }} sx={{ textTransform: "none", fontWeight: 500 }} > - Disconnect + {t("wallet.disconnect")} diff --git a/src/vva-fe/src/components/organisms/ChooseStakeKeyPanel.tsx b/src/vva-fe/src/components/organisms/ChooseStakeKeyPanel.tsx index fbc07b1b4..9e4daaf78 100644 --- a/src/vva-fe/src/components/organisms/ChooseStakeKeyPanel.tsx +++ b/src/vva-fe/src/components/organisms/ChooseStakeKeyPanel.tsx @@ -8,6 +8,7 @@ import { PATHS } from "@consts"; import { setItemToLocalStorage, WALLET_LS_KEY } from "@utils"; import { theme } from "@/theme"; import { useScreenDimension } from "@hooks"; +import { usei18n } from "@translations"; export const ChooseStakeKeyPanel = () => { const { disconnectWallet, stakeKeys, setStakeKey } = useCardano(); @@ -15,6 +16,7 @@ export const ChooseStakeKeyPanel = () => { const { addSuccessAlert } = useSnackbar(); const [chosenKey, setChosenKey] = useState(""); const { isMobile } = useScreenDimension(); + const { t } = usei18n(); const { palette: { boxShadow2 }, } = theme; @@ -36,7 +38,7 @@ export const ChooseStakeKeyPanel = () => { px: isMobile ? 0 : 6, }} > - Cancel + {t("cancel")} ); }, [isMobile]); @@ -61,7 +63,7 @@ export const ChooseStakeKeyPanel = () => { }} variant="contained" > - Select + {t("select")} ); }, [isMobile, chosenKey, setStakeKey]); @@ -83,10 +85,10 @@ export const ChooseStakeKeyPanel = () => { > - Pick Stake Key + {t("wallet.pickStakeKey")} - Select the stake key you want to use: + {t("wallet.pickStakeKey")} { if (!window.cardano) return []; const keys = Object.keys(window.cardano); @@ -15,7 +18,9 @@ export function ChooseWalletModal() { const { icon, name, supportedExtensions } = window.cardano[k]; if (icon && name && supportedExtensions) { // Check if the name already exists in resultWallets - const isNameDuplicate = resultWallets.some(wallet => wallet.label === name); + const isNameDuplicate = resultWallets.some( + (wallet) => wallet.label === name + ); // Check if the supportedExtensions array contains an entry with cip === 95 const isCip95Available = Boolean( supportedExtensions?.find((i) => i.cip === 95) @@ -36,7 +41,7 @@ export function ChooseWalletModal() { return ( - Connect your Wallet + {t("wallet.connectYourWallet")} - Choose the wallet you want to connect with: + {t("wallet.chooseWallet")} - You don't have wallets to connect, install a wallet and refresh - the page and try again + {t("wallet.noWalletsToConnect")} ) : ( walletOptions.map(({ icon, label, name, cip95Available }) => { @@ -91,8 +95,7 @@ export function ChooseWalletModal() { textAlign: "center", }} > - Can’t see your wallet? Check what wallets are currently compatible - with GovTool{" "} + {t("wallet.cantSeeWalletQuestion")} - here + {t("here")} . diff --git a/src/vva-fe/src/components/organisms/DashboardCards.tsx b/src/vva-fe/src/components/organisms/DashboardCards.tsx index e29782299..aeffdbea9 100644 --- a/src/vva-fe/src/components/organisms/DashboardCards.tsx +++ b/src/vva-fe/src/components/organisms/DashboardCards.tsx @@ -3,11 +3,15 @@ import { Box, CircularProgress, Typography } from "@mui/material"; import { IMAGES, PATHS } from "@consts"; import { useCardano, useModal } from "@context"; -import { useGetAdaHolderVotingPowerQuery, useScreenDimension } from "@hooks"; +import { + useGetAdaHolderVotingPowerQuery, + useScreenDimension, + useGetAdaHolderCurrentDelegationQuery, +} from "@hooks"; import { DashboardActionCard } from "@molecules"; import { useCallback, useMemo, useState } from "react"; -import { useGetAdaHolderCurrentDelegationQuery } from "@hooks"; import { correctAdaFormat, formHexToBech32, openInNewTab } from "@utils"; +import { TypedTrans, usei18n } from "@translations"; export const DashboardCards = () => { const { @@ -31,6 +35,7 @@ export const DashboardCards = () => { const [isLoading, setIsLoading] = useState(false); const { votingPower, powerIsLoading } = useGetAdaHolderVotingPowerQuery(stakeKey); + const { t } = usei18n(); const retireAsDrep = useCallback(async () => { try { @@ -48,11 +53,10 @@ export const DashboardCards = () => { type: "statusModal", state: { status: "success", - title: "Retirement Transaction Submitted!", - message: - "The confirmation of your retirement might take a bit of time but you can track it using.", + title: t("modals.retirement.title"), + message: t("modals.retirement.message"), link: `https://adanordic.com/latest_transactions`, - buttonText: "Go to dashboard", + buttonText: t("modals.common.goToDashboard"), dataTestId: "retirement-transaction-submitted-modal", }, }); @@ -65,8 +69,8 @@ export const DashboardCards = () => { state: { status: "warning", message: errorMessage, - buttonText: "Go to dashboard", - title: "Oops!", + buttonText: t("modals.common.goToDashboard"), + title: t("modals.common.oops"), dataTestId: "retirement-transaction-error-modal", }, }); @@ -76,43 +80,41 @@ export const DashboardCards = () => { }, [buildDRepRetirementCert, buildSignSubmitConwayCertTx]); const delegationDescription = useMemo(() => { - const correctAdaRepresentation = ( - {correctAdaFormat(votingPower)} - ); + const correctAdaRepresentation = correctAdaFormat(votingPower); if (currentDelegation === dRepID) { return ( - <> - You have delegated your voting power of ₳{correctAdaRepresentation} to - yourself. - + ); } else if (currentDelegation === "drep_always_no_confidence") { return ( - <> - You have delegated your voting power of ₳{correctAdaRepresentation}. - You are going to vote 'NO' as default. - + ); } else if (currentDelegation === "drep_always_abstain") { return ( - <> - You have delegated your voting power of ₳{correctAdaRepresentation}. - You are going to vote 'ABSTAIN' as default. - + ); } else if (currentDelegation) { return ( - <> - You have delegated your voting power of ₳{correctAdaRepresentation} to - a selected DRep. - + ); } else { return ( - <> - If you want to delegate your own voting power of ₳ - {correctAdaRepresentation}. - + ); } }, [currentDelegation, drepId, votingPower]); @@ -132,41 +134,37 @@ export const DashboardCards = () => { }, [currentDelegation, drepId, votingPower]); const progressDescription = useMemo(() => { - const correctAdaRepresentation = ( - {correctAdaFormat(votingPower)} - ); + const correctAdaRepresentation = correctAdaFormat(votingPower); if (delegateTo === dRepID) { return ( - <> - Your own voting power of ₳{correctAdaRepresentation} is in progress of - being delegated. You are going to delegate your voting power to - yourself. - + ); } if (delegateTo === "no confidence") { return ( - <> - Your own voting power of ₳{correctAdaRepresentation} is in progress of - being delegated. You are going to vote ‘NO’ as default. - + ); } if (delegateTo === "abstain") { return ( - <> - Your own voting power of ₳{correctAdaRepresentation} is in progress of - being delegated. You are going to vote ‘ABSTAIN’ as default. - + ); } if (delegateTo) { return ( - <> - Your own voting power of ₳{correctAdaRepresentation} is progress of - being delegated. You are going to delegate your voting power to a - selected DRep. - + ); } }, [delegateTo, votingPower]); @@ -213,20 +211,22 @@ export const DashboardCards = () => { return ( <> - See Active Governance Actions + {t("dashboard.headingTwo")} navigate(PATHS.dashboard_governance_actions) } - firstButtonLabel={ - dRep?.isRegistered ? "Review and vote" : "View governance actions" - } + firstButtonLabel={t( + `dashboard.govActions.${ + dRep?.isRegistered ? "reviewAndVote" : "view" + }` + )} imageURL={IMAGES.govActionListImage} - title="View Governance Actions" + title={t("dashboard.govActions.title")} /> {screenWidth < 1024 ? null : ( <> @@ -256,7 +256,7 @@ export const DashboardCards = () => { > {dRep?.isRegistered && renderGovActionSection()} - Your Participation + {t("dashboard.headingOne")} { delegateTransaction?.transactionHash ? "" : currentDelegation - ? "Change delegation" - : "Delegate" + ? t("dashboard.delegation.changeDelegation") + : t("delegate") } imageHeight={55} imageWidth={65} @@ -293,7 +293,7 @@ export const DashboardCards = () => { imageURL={IMAGES.govActionDelegateImage} cardId={displayedDelegationId} inProgress={!!delegateTransaction?.transactionHash} - cardTitle="DRep you delegated to" + cardTitle={t("dashboard.delegation.dRepDelegatedTo")} secondButtonAction={ delegateTransaction?.transactionHash ? () => openInNewTab("https://adanordic.com/latest_transactions") @@ -304,20 +304,18 @@ export const DashboardCards = () => { } secondButtonLabel={ delegateTransaction?.transactionHash - ? "See transaction" + ? t("seeTransaction") : currentDelegation ? "" - : "Learn more" + : t("learnMore") } title={ delegateTransaction?.transactionHash ? ( - "Voting Power Delegation" + t("dashboard.delegation.votingPowerDelegation") ) : currentDelegation ? ( - <> - Your Voting Power is Delegated - + ) : ( - "Use your Voting Power" + t("dashboard.delegation.useYourVotingPower") ) } /> @@ -341,19 +339,21 @@ export const DashboardCards = () => { ? "change-metadata-button" : "register-learn-more-button" } - description={ - registerTransaction.transactionHash - ? registerTransaction?.type === "retirement" - ? "The retirement process is ongoing. This may take several minutes." - : registerTransaction?.type === "registration" - ? "The registration process is ongoing. This may take several minutes." - : "The update DRep metadata is ongoing. This may take several minutes." - : dRep?.isRegistered - ? "Ada holders can delegate their voting power to you." - : dRep?.wasRegistered - ? "Ada holders can delegate their voting power to you." - : "If you want to directly participate in voting and have other ada holders delegate their voting power to you." - } + description={t( + `dashboard.registration.${ + registerTransaction.transactionHash + ? registerTransaction?.type === "retirement" + ? "retirementInProgress" + : registerTransaction?.type === "registration" + ? "registrationInProgress" + : "metadataUpdateInProgress" + : dRep?.isRegistered + ? "holdersCanDelegate" + : dRep?.wasRegistered + ? "holdersCanDelegate" + : "ifYouWant" + }` + )} firstButtonAction={ dRep?.isRegistered ? retireAsDrep @@ -362,9 +362,11 @@ export const DashboardCards = () => { firstButtonLabel={ registerTransaction?.transactionHash ? "" - : dRep?.isRegistered - ? "Retire as a DRep" - : "Register" + : t( + `dashboard.registration.${ + dRep?.isRegistered ? "retire" : "register" + }` + ) } inProgress={!!registerTransaction?.transactionHash} imageURL={IMAGES.govActionRegisterImage} @@ -382,28 +384,30 @@ export const DashboardCards = () => { } secondButtonLabel={ registerTransaction?.transactionHash - ? "See transaction" + ? t("seeTransaction") : dRep?.isRegistered - ? "Change metadata" - : "Learn more" + ? t("dashboard.registration.changeMetadata") + : t("learnMore") } cardId={dRep?.isRegistered || dRep?.wasRegistered ? drepId : ""} cardTitle={ - dRep?.isRegistered || dRep?.wasRegistered ? "My DRep ID" : "" - } - title={ - registerTransaction?.transactionHash - ? registerTransaction?.type === "retirement" - ? "DRep Retirement" - : registerTransaction?.type === "registration" - ? "DRep Registration" - : "DRep Update" - : dRep?.isRegistered - ? "You are Registered as a DRep" - : dRep?.wasRegistered - ? "Register Again as a dRep" - : "Register as a DRep" + dRep?.isRegistered || dRep?.wasRegistered ? t("myDRepId") : "" } + title={t( + `dashboard.registration.${ + registerTransaction?.transactionHash + ? registerTransaction?.type === "retirement" + ? "dRepRetirement" + : registerTransaction?.type === "registration" + ? "dRepRegistration" + : "dRepUpdate" + : dRep?.isRegistered + ? "youAreRegistered" + : dRep?.wasRegistered + ? "registerAgain" + : "registerAsDRep" + }` + )} /> {!dRep?.isRegistered && renderGovActionSection()} diff --git a/src/vva-fe/src/components/organisms/DashboardGovernanceActionDetails.tsx b/src/vva-fe/src/components/organisms/DashboardGovernanceActionDetails.tsx index e952b5ab4..36e751719 100644 --- a/src/vva-fe/src/components/organisms/DashboardGovernanceActionDetails.tsx +++ b/src/vva-fe/src/components/organisms/DashboardGovernanceActionDetails.tsx @@ -22,12 +22,14 @@ import { getShortenedGovActionId, getProposalTypeLabel, } from "@utils"; +import { usei18n } from "@translations"; export const DashboardGovernanceActionDetails = () => { const { dRep } = useCardano(); const { state, hash } = useLocation(); const navigate = useNavigate(); const { isMobile, screenWidth } = useScreenDimension(); + const { t } = usei18n(); const { proposalId } = useParams(); const fullProposalId = proposalId + hash; @@ -45,11 +47,11 @@ export const DashboardGovernanceActionDetails = () => { style={{ textDecorationColor: "#0033AD" }} > - Governance Actions + {t("govActions.title")} , - Vote on Governance Action + {t("govActions.voteOnGovActions")} , ]; @@ -100,7 +102,7 @@ export const DashboardGovernanceActionDetails = () => { style={{ marginRight: "12px", transform: "rotate(180deg)" }} /> - Back to the list + {t("backToList")} @@ -144,12 +146,14 @@ export const DashboardGovernanceActionDetails = () => { ) : ( - Governnance action with id  + {t("govActions.withIdNotExist.partOne")}  {` ${shortenedGovActionId} `} -  does not exist. + +  {t("govActions.withIdNotExist.partTwo")} + )} diff --git a/src/vva-fe/src/components/organisms/DashboardGovernanceActions.tsx b/src/vva-fe/src/components/organisms/DashboardGovernanceActions.tsx index 8f1a456d5..4a4b2a2b3 100644 --- a/src/vva-fe/src/components/organisms/DashboardGovernanceActions.tsx +++ b/src/vva-fe/src/components/organisms/DashboardGovernanceActions.tsx @@ -9,6 +9,7 @@ import { GovernanceActionsToVote, DashboardGovernanceActionsVotedOn, } from "@organisms"; +import { usei18n } from "@translations"; interface TabPanelProps { children?: React.ReactNode; @@ -67,6 +68,7 @@ export const DashboardGovernanceActions = () => { const { dRep } = useCardano(); const { isMobile } = useScreenDimension(); + const { t } = usei18n(); const handleChange = (_event: React.SyntheticEvent, newValue: number) => { setContent(newValue); @@ -123,7 +125,7 @@ export const DashboardGovernanceActions = () => { > { /> { if (data.length && searchPhrase) { @@ -53,12 +55,11 @@ export const DashboardGovernanceActionsVotedOn = ({ <> {!data.length ? ( - You haven't voted on any Governance Actions yet. Check the 'To - vote on' section to vote on Governance Actions. + {t("govActions.youHaventVotedYet")} ) : !filteredData?.length ? ( - No results for the search. + {t("govActions.noResults")} ) : ( <> diff --git a/src/vva-fe/src/components/organisms/DashboardTopNav.tsx b/src/vva-fe/src/components/organisms/DashboardTopNav.tsx index b3389cd38..e933c1189 100644 --- a/src/vva-fe/src/components/organisms/DashboardTopNav.tsx +++ b/src/vva-fe/src/components/organisms/DashboardTopNav.tsx @@ -8,6 +8,7 @@ import { ICONS, PATHS } from "@consts"; import { useCardano } from "@context"; import { DRepInfoCard, WalletInfoCard } from "@molecules"; import { openInNewTab } from "@utils"; +import { usei18n } from "@translations"; type DashboardTopNavProps = { imageSRC?: string; @@ -30,6 +31,7 @@ export const DashboardTopNav = ({ const { dRep } = useCardano(); const navigate = useNavigate(); const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const { t } = usei18n(); return ( { setIsDrawerOpen(false); @@ -126,7 +128,7 @@ export const DashboardTopNav = ({ { setIsDrawerOpen(false); @@ -138,7 +140,7 @@ export const DashboardTopNav = ({ { openInNewTab( @@ -153,7 +155,7 @@ export const DashboardTopNav = ({ { openInNewTab("https://docs.sanchogov.tools/faqs"); diff --git a/src/vva-fe/src/components/organisms/DelegateTodRepStepOne.tsx b/src/vva-fe/src/components/organisms/DelegateTodRepStepOne.tsx index 6febb38ae..5c875ea8b 100644 --- a/src/vva-fe/src/components/organisms/DelegateTodRepStepOne.tsx +++ b/src/vva-fe/src/components/organisms/DelegateTodRepStepOne.tsx @@ -11,8 +11,8 @@ import { useScreenDimension, } from "@hooks"; import { theme } from "@/theme"; -import { tooltips } from "@/consts/texts"; import { correctAdaFormat } from "@utils"; +import { usei18n } from "@translations"; interface DelegateProps { setStep: (newStep: number) => void; @@ -35,6 +35,7 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { palette: { boxShadow2 }, } = theme; const { isMobile } = useScreenDimension(); + const { t } = usei18n(); const { votingPower } = useGetAdaHolderVotingPowerQuery(stakeKey); const correctAdaRepresentation = correctAdaFormat(votingPower); @@ -44,9 +45,8 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { type: "statusModal", state: { status: "success", - title: "Delegation Transaction Submitted!", - message: - "The confirmation of your actual delegation might take a bit of time but you can track it using.", + title: t("modals.delegation.title"), + message: t("modals.delegation.message"), link: "https://adanordic.com/latest_transactions", buttonText: "Go to dashboard", onSubmit: () => { @@ -63,10 +63,10 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { type: "statusModal", state: { status: "warning", - title: "Oops!", + title: t("modals.delegation.title"), message: errorMessage, isWarning: true, - buttonText: "Go to dashboard", + buttonText: t("modals.common.goToDashboard"), onSubmit: () => { navigate(PATHS.dashboard); closeModal(); @@ -121,7 +121,7 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { }} variant="contained" > - {chosenOption !== dRepID ? "Next step" : "Delegate"} + {chosenOption !== dRepID ? t("nextStep") : t("delegate")} ); }, [chosenOption, dRep?.isRegistered, isMobile, delegate, dRepID]); @@ -138,7 +138,7 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { }} variant="outlined" > - Cancel + {t("cancel")} ); }, [isMobile]); @@ -171,7 +171,7 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { > - Voting power to delegate: + {t("delegation.votingPowerToDelegate")} { )} - Use your Voting Power + {t("delegation.heading")} - You can delegate your voting power to a DRep or to a pre-defined - voting option. + {t("delegation.description")} { @@ -220,11 +219,11 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { @@ -242,7 +241,9 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { }, ]} > - Other options + + {t("delegation.otherOptions")} + arrow { <> @@ -271,11 +274,13 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { diff --git a/src/vva-fe/src/components/organisms/DelegateTodRepStepTwo.tsx b/src/vva-fe/src/components/organisms/DelegateTodRepStepTwo.tsx index 6ac689264..de0cd0701 100644 --- a/src/vva-fe/src/components/organisms/DelegateTodRepStepTwo.tsx +++ b/src/vva-fe/src/components/organisms/DelegateTodRepStepTwo.tsx @@ -5,6 +5,7 @@ import { Button, Input, Typography } from "../atoms"; import { useScreenDimension, useDelegateTodRepForm } from "@hooks"; import { theme } from "@/theme"; import { openInNewTab } from "@utils"; +import { usei18n } from "@translations"; interface DelegateProps { setStep: (newStep: number) => void; @@ -12,6 +13,7 @@ interface DelegateProps { export const DelegateTodRepStepTwo = ({ setStep }: DelegateProps) => { const { isMobile } = useScreenDimension(); + const { t } = usei18n(); const { palette: { boxShadow2 }, @@ -33,7 +35,7 @@ export const DelegateTodRepStepTwo = ({ setStep }: DelegateProps) => { }} variant="contained" > - Delegate + {t("delegate")} ); }, [isDelegateButtonDisabled, delegate, isMobile]); @@ -50,7 +52,7 @@ export const DelegateTodRepStepTwo = ({ setStep }: DelegateProps) => { }} variant="outlined" > - Back + {t("back")} ); }, [isMobile]); @@ -69,20 +71,20 @@ export const DelegateTodRepStepTwo = ({ setStep }: DelegateProps) => { > - Paste DRep ID + {t("delegation.pasteDRepId")} - The DRep ID is the identifier of a DRep. + {t("delegation.dRepIdDescription")} @@ -99,7 +101,7 @@ export const DelegateTodRepStepTwo = ({ setStep }: DelegateProps) => { sx={[{ "&:hover": { cursor: "pointer" } }]} > - Where can I find a DRep ID? + {t("delegation.whereFindDRepId")} diff --git a/src/vva-fe/src/components/organisms/Drawer.tsx b/src/vva-fe/src/components/organisms/Drawer.tsx index ee8ec677f..40e275312 100644 --- a/src/vva-fe/src/components/organisms/Drawer.tsx +++ b/src/vva-fe/src/components/organisms/Drawer.tsx @@ -6,9 +6,11 @@ import { CONNECTED_NAV_ITEMS, ICONS, IMAGES, PATHS } from "@consts"; import { useCardano } from "@context"; import { WalletInfoCard, DRepInfoCard } from "@molecules"; import { openInNewTab } from "@/utils"; +import { usei18n } from "@translations"; export const Drawer = () => { const { dRep } = useCardano(); + const { t } = usei18n(); return ( @@ -65,7 +67,7 @@ export const Drawer = () => { { /> - © 2023 Voltaire Gov Tool + {t("footer.copyright")} diff --git a/src/vva-fe/src/components/organisms/DrawerMobile.tsx b/src/vva-fe/src/components/organisms/DrawerMobile.tsx index f43174244..2d7a7e27d 100644 --- a/src/vva-fe/src/components/organisms/DrawerMobile.tsx +++ b/src/vva-fe/src/components/organisms/DrawerMobile.tsx @@ -6,6 +6,7 @@ import { ICONS, IMAGES, NAV_ITEMS } from "@consts"; import { useScreenDimension } from "@hooks"; import { useModal } from "@context"; import { openInNewTab } from "@utils"; +import { usei18n } from "@translations"; type DrawerMobileProps = { isConnectButton: boolean; @@ -23,6 +24,7 @@ export const DrawerMobile = ({ }: DrawerMobileProps) => { const { screenWidth } = useScreenDimension(); const { openModal } = useModal(); + const { t } = usei18n(); return ( - Connect your wallet + {t("wallet.connectYourWalletButton")} ) : null} diff --git a/src/vva-fe/src/components/organisms/ExternalLinkModal.tsx b/src/vva-fe/src/components/organisms/ExternalLinkModal.tsx index b0cf54d94..072aaf6a3 100644 --- a/src/vva-fe/src/components/organisms/ExternalLinkModal.tsx +++ b/src/vva-fe/src/components/organisms/ExternalLinkModal.tsx @@ -6,6 +6,7 @@ import { useModal } from "@context"; import { useScreenDimension } from "@hooks"; import { theme } from "@/theme"; import { openInNewTab } from "@utils"; +import { usei18n } from "@translations"; export interface ExternalLinkModalState { externalLink: string; @@ -14,6 +15,7 @@ export interface ExternalLinkModalState { export function ExternalLinkModal() { const { state, closeModal } = useModal(); const { isMobile } = useScreenDimension(); + const { t } = usei18n(); const { palette: { primaryBlue, fadedPurple }, } = theme; @@ -26,13 +28,15 @@ export function ExternalLinkModal() { style={{ height: "84px", margin: "0 auto", width: "84px" }} /> - {isMobile ? "External Link Safety" : "Be Careful!"} + {isMobile + ? t("modals.externalLink.safety") + : t("modals.externalLink.beCareful")} {isMobile - ? "This is an external link:" - : "You are about to open an external link to:"} + ? t("modals.externalLink.thisIs") + : t("modals.externalLink.youAreAboutToOpen")} - Exercise caution and verify the website's authenticity before sharing - personal information. To proceed, click 'Continue'. To stay on - Voltaire, click 'Cancel'. + {t("modals.externalLink.description")} - {isMobile ? "Continue" : "Continue to external link"} + {isMobile ? t("continue") : t("modals.externalLink.continueTo")} diff --git a/src/vva-fe/src/components/organisms/Footer.tsx b/src/vva-fe/src/components/organisms/Footer.tsx index c5ce46466..e4d28f69c 100644 --- a/src/vva-fe/src/components/organisms/Footer.tsx +++ b/src/vva-fe/src/components/organisms/Footer.tsx @@ -3,9 +3,11 @@ import { Box, Link } from "@mui/material"; import { Typography } from "@atoms"; import { useScreenDimension } from "@hooks"; import { openInNewTab } from "@utils"; +import { usei18n } from "@translations"; export const Footer = () => { const { isMobile, pagePadding } = useScreenDimension(); + const { t } = usei18n(); return ( { > - © 2023 Voltaire Gov Tool + {t("footer.copyright")} @@ -34,7 +36,7 @@ export const Footer = () => { sx={{ "&:hover": { color: "primaryBlue", cursor: "pointer" } }} variant="caption" > - Privacy policy + {t("footer.privacyPolicy")} diff --git a/src/vva-fe/src/components/organisms/GovernanceActionDetailsCard.tsx b/src/vva-fe/src/components/organisms/GovernanceActionDetailsCard.tsx index 24c594043..de7c54ea1 100644 --- a/src/vva-fe/src/components/organisms/GovernanceActionDetailsCard.tsx +++ b/src/vva-fe/src/components/organisms/GovernanceActionDetailsCard.tsx @@ -6,7 +6,7 @@ import { GovActionDetails, VoteActionForm, VotesSubmitted } from "../molecules"; import { useModal } from "@context"; import { ICONS } from "@consts"; import { Tooltip } from "@atoms"; -import { tooltips } from "@/consts/texts"; +import { usei18n } from "@translations"; type GovernanceActionDetailsCardProps = { abstainVotes: number; @@ -37,6 +37,7 @@ export const GovernanceActionDetailsCard = ({ }: GovernanceActionDetailsCardProps) => { const { screenWidth } = useScreenDimension(); const { openModal } = useModal(); + const { t } = usei18n(); return ( - Submission date: + {t("govActions.submissionDate")} {createdDate} @@ -109,15 +110,15 @@ export const GovernanceActionDetailsCard = ({ width={"100%"} > - Expiry date: + {t("govActions.expiryDate")} {expiryDate} @@ -134,7 +135,7 @@ export const GovernanceActionDetailsCard = ({ - Governance Action Type: + {t("govActions.type")} @@ -144,7 +145,7 @@ export const GovernanceActionDetailsCard = ({ - Governance Action ID: + {t("govActions.id")} - Governance Details: + {t("govActions.details")} {typeof details === "object" && details !== null ? ( Object.entries(details).map(([key, value]) => { @@ -200,7 +201,7 @@ export const GovernanceActionDetailsCard = ({ data-testid="view-other-details-button" > - View other details + {t("govActions.viewOtherDetails")} external link 0 ? filters : defaultCategories; @@ -94,7 +96,7 @@ export const GovernanceActionsToVote = ({ <> {!mappedData.length ? ( - No results for the search + {t("govActions.noResults")} ) : ( <> diff --git a/src/vva-fe/src/components/organisms/Hero.tsx b/src/vva-fe/src/components/organisms/Hero.tsx index a1a0f1bfe..ecc4b5c8c 100644 --- a/src/vva-fe/src/components/organisms/Hero.tsx +++ b/src/vva-fe/src/components/organisms/Hero.tsx @@ -5,12 +5,14 @@ import { Button, Typography } from "@atoms"; import { IMAGES, PATHS } from "@consts"; import { useCardano, useModal } from "@context"; import { useScreenDimension } from "@hooks"; +import { usei18n } from "@translations"; export const Hero = () => { const { isEnabled } = useCardano(); const { openModal } = useModal(); const navigate = useNavigate(); const { isMobile, screenWidth, pagePadding } = useScreenDimension(); + const { t } = usei18n(); const IMAGE_SIZE = screenWidth < 768 ? 140 @@ -36,24 +38,17 @@ export const Hero = () => { - SanchoNet -
- Governance Tool + {t("hero.headline")}
- Interact with SanchoNet using GovTool - a friendly user{" "} - {!isMobile &&
} - interface connected to SanchoNet. You can delegate{" "} - {!isMobile &&
} - your voting power (tAda) or become a SanchoNet DRep{" "} - {!isMobile &&
} - to allow people to delegate voting power to you. + {t("hero.description")}
{ const navigate = useNavigate(); const { openModal } = useModal(); const { isMobile, screenWidth } = useScreenDimension(); + const { t } = usei18n(); return ( { openModal({ type: "chooseWallet" })} - firstButtonLabel="Connect to delegate" + firstButtonLabel={t("dashboard.delegation.connectToDelegate")} imageHeight={80} imageURL={IMAGES.govActionDelegateImage} imageWidth={115} @@ -48,8 +50,8 @@ export const HomeCards = () => { "https://docs.sanchogov.tools/faqs/ways-to-use-your-voting-power" ) } - secondButtonLabel="Learn more" - title="Use your Voting Power" + secondButtonLabel={t("learnMore")} + title={t("dashboard.delegation.useYourVotingPower")} /> { openModal({ type: "chooseWallet" })} - firstButtonLabel="Connect to register" + firstButtonLabel={t("dashboard.registration.connectToRegister")} imageHeight={80} imageURL={IMAGES.govActionRegisterImage} imageWidth={70} @@ -70,8 +72,8 @@ export const HomeCards = () => { "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep" ) } - secondButtonLabel="Learn more" - title="Register as a DRep" + secondButtonLabel={t("learnMore")} + title={t("dashboard.registration.registerAsDRep")} /> { > navigate(PATHS.governance_actions)} - firstButtonLabel="View governance actions" + firstButtonLabel={t("dashboard.govActions.view")} imageHeight={80} imageURL={IMAGES.govActionListImage} imageWidth={80} - title="Governance Actions" + title={t("dashboard.govActions.title")} /> diff --git a/src/vva-fe/src/components/organisms/RegisterAsdRepStepOne.tsx b/src/vva-fe/src/components/organisms/RegisterAsdRepStepOne.tsx index df466b4db..9f7d872a0 100644 --- a/src/vva-fe/src/components/organisms/RegisterAsdRepStepOne.tsx +++ b/src/vva-fe/src/components/organisms/RegisterAsdRepStepOne.tsx @@ -7,6 +7,7 @@ import { PATHS } from "@consts"; import { useScreenDimension, useRegisterAsdRepFormContext } from "@hooks"; import { theme } from "@/theme"; import { openInNewTab } from "@utils"; +import { usei18n } from "@translations"; interface Props { setStep: Dispatch>; @@ -14,6 +15,7 @@ interface Props { export const RegisterAsdRepStepOne = ({ setStep }: Props) => { const navigate = useNavigate(); + const { t } = usei18n(); const { palette: { boxShadow2 }, } = theme; @@ -33,7 +35,7 @@ export const RegisterAsdRepStepOne = ({ setStep }: Props) => { }} variant="outlined" > - Cancel + {t("cancel")} ); }, [isMobile]); @@ -53,7 +55,7 @@ export const RegisterAsdRepStepOne = ({ setStep }: Props) => { }} variant="contained" > - {showSubmitButton ? "Confirm" : "Skip"} + {showSubmitButton ? t("confirm") : t("skip")} ); }, [isMobile, isValid, showSubmitButton]); @@ -74,23 +76,22 @@ export const RegisterAsdRepStepOne = ({ setStep }: Props) => { sx={{ letterSpacing: 1.5, textAlign: "center" }} variant="body1" > - OPTIONAL + {t("registration.optional")} - Add Information + {t("registration.headingStepOne")} - You can include extra information about yourself by adding a URL and - its hash. + {t("registration.descriptionStepOne")} { { sx={{ cursor: "pointer" }} > - How to create URL and hash? + {t("forms.howCreateUrlAndHash")}
diff --git a/src/vva-fe/src/components/organisms/RegisterAsdRepStepTwo.tsx b/src/vva-fe/src/components/organisms/RegisterAsdRepStepTwo.tsx index 60ddbdd85..c51c6abaa 100644 --- a/src/vva-fe/src/components/organisms/RegisterAsdRepStepTwo.tsx +++ b/src/vva-fe/src/components/organisms/RegisterAsdRepStepTwo.tsx @@ -4,6 +4,7 @@ import { Box } from "@mui/material"; import { LoadingButton, Button, Typography } from "@atoms"; import { theme } from "@/theme"; import { useRegisterAsdRepFormContext, useScreenDimension } from "@hooks"; +import { usei18n } from "@translations"; interface Props { setStep: Dispatch>; @@ -15,6 +16,7 @@ export const RegisterAsdRepStepTwo = ({ setStep }: Props) => { } = theme; const { isLoading, submitForm } = useRegisterAsdRepFormContext(); const { isMobile, pagePadding, screenWidth } = useScreenDimension(); + const { t } = usei18n(); const renderBackButton = useMemo(() => { return ( @@ -28,7 +30,7 @@ export const RegisterAsdRepStepTwo = ({ setStep }: Props) => { }} variant="outlined" > - Back + {t("back")} ); }, [isMobile]); @@ -48,7 +50,7 @@ export const RegisterAsdRepStepTwo = ({ setStep }: Props) => { }} variant="contained" > - Register + {t("registration.register")} ); }, [isLoading, isMobile, submitForm]); @@ -65,19 +67,19 @@ export const RegisterAsdRepStepTwo = ({ setStep }: Props) => { > - Confirm DRep registration + {t("registration.headingStepTwo")} - By clicking register you create your DRep ID within your wallet and - become a DRep.
-
- Once the registration has completed your DRep ID will be shown on your - dashboard. You will be able to share your DRep ID so that other ada - holders can delegate their voting power to you. + {t("registration.descriptionStepTwo")}
- Show all + {t("slider.showAll")} )} @@ -171,7 +173,7 @@ export const Slider = ({ } > - View all + {t("slider.viewAll")} (); const { isMobile } = useScreenDimension(); + const { t } = usei18n(); return ( @@ -49,7 +51,7 @@ export function StatusModal() { target="_blank" sx={[{ "&:hover": { cursor: "pointer" } }]} > - this link + {t("thisLink")} )} @@ -66,7 +68,7 @@ export function StatusModal() { }} variant="contained" > - {state?.buttonText || "Confirm"} + {state?.buttonText || t("confirm")} ); diff --git a/src/vva-fe/src/components/organisms/TopNav.tsx b/src/vva-fe/src/components/organisms/TopNav.tsx index c7c42f2e2..e918c433c 100644 --- a/src/vva-fe/src/components/organisms/TopNav.tsx +++ b/src/vva-fe/src/components/organisms/TopNav.tsx @@ -8,6 +8,7 @@ import { useCardano, useModal } from "@context"; import { useScreenDimension } from "@hooks"; import { DrawerMobile } from "./DrawerMobile"; import { openInNewTab } from "@utils"; +import { usei18n } from "@translations"; const POSITION_TO_BLUR = 50; @@ -18,6 +19,7 @@ export const TopNav = ({ isConnectButton = true }) => { const { screenWidth, isMobile } = useScreenDimension(); const { isEnabled, disconnectWallet, stakeKey } = useCardano(); const navigate = useNavigate(); + const { t } = usei18n(); useEffect(() => { const onScroll = () => { @@ -103,7 +105,7 @@ export const TopNav = ({ isConnectButton = true }) => { size="extraLarge" variant="contained" > - Connect wallet + {t("wallet.connectWallet")}
) : null} @@ -125,7 +127,7 @@ export const TopNav = ({ isConnectButton = true }) => { }} variant="contained" > - Connect + {t("wallet.connect")} ) : null} (); + const { isMobile } = useScreenDimension(); + const { t } = usei18n(); + const VOTES = [ { title: "yes", vote: state?.yesVotes }, { title: "abstain", vote: state?.abstainVotes }, { title: "no", vote: state?.noVotes }, ]; - const { isMobile } = useScreenDimension(); return ( - Governance Action votes + {t("modals.votingPower.govActionsVotes")} - Votes submitted by DReps + {t("modals.votingPower.votesSubmittedByDReps")} {VOTES.map((vote, index) => ( - Your vote + {t("modals.votingPower.yourVote")} ) : null} ([]); const [{ messageInfo, open }, setState] = useState(defaultState); const { isMobile } = useScreenDimension(); + const { t } = usei18n(); const addWarningAlert = useCallback( (message: string, autoHideDuration = DEFAULT_AUTO_HIDE_DURATION) => @@ -97,7 +99,7 @@ function SnackbarProvider({ children }: ProviderProps) { ); const addChangesSavedAlert = useCallback( - () => addSuccessAlert("Your changes have been saved"), + () => addSuccessAlert(t("alerts.changesSaved")), [addSuccessAlert] ); diff --git a/src/vva-fe/src/context/wallet.tsx b/src/vva-fe/src/context/wallet.tsx index 46529d0af..1f511f058 100644 --- a/src/vva-fe/src/context/wallet.tsx +++ b/src/vva-fe/src/context/wallet.tsx @@ -71,6 +71,7 @@ import { setLimitedDelegationInterval, setLimitedRegistrationInterval, } from "./walletUtils"; +import { TypedTrans, usei18n } from "@translations"; interface Props { children: React.ReactNode; @@ -203,6 +204,7 @@ function CardanoProvider(props: Props) { const [isDrepLoading, setIsDrepLoading] = useState(true); const { addSuccessAlert, addWarningAlert, addErrorAlert } = useSnackbar(); + const { t } = usei18n(); const isPendingTransaction = useCallback(() => { if ( @@ -214,10 +216,9 @@ function CardanoProvider(props: Props) { type: "statusModal", state: { status: "info", - title: "Please wait for your previous transaction to be completed.", - message: - "Before performing a new action please wait for the previous action transaction to be completed.", - buttonText: "Ok", + title: t("modals.waitForTransaction.title"), + message: t("modals.waitForTransaction.message"), + buttonText: t("ok"), onSubmit: () => { closeModal(); }, @@ -284,13 +285,9 @@ function CardanoProvider(props: Props) { stakeKey ).then((isDelegated) => { if (isDelegated) { - addSuccessAlert( - "Your voting power has been successfully delegated!" - ); + addSuccessAlert(t("alerts.delegation.success")); } else { - addWarningAlert( - "Your voting power has been successfully delegated! Please refresh the page." - ); + addWarningAlert(t("alerts.delegation.refreshPage")); } }); } @@ -301,7 +298,7 @@ function CardanoProvider(props: Props) { TIME_TO_EXPIRE_TRANSACTION ) { resetDelegateTransaction(); - if (isEnabled) addErrorAlert("Delegation transaction failed"); + if (isEnabled) addErrorAlert(t("alerts.delegation.failed")); } }; let interval = setInterval(checkDelegateTransaction, REFRESH_TIME); @@ -336,28 +333,20 @@ function CardanoProvider(props: Props) { ).then((isRegistered) => { if (registerTransaction.type === "registration") { if (isRegistered) { - addSuccessAlert( - "You have successfully registered as a DRep!" - ); + addSuccessAlert(t("alerts.registration.success")); } else { - addWarningAlert( - "You have successfully registered as a DRep! Please refresh the page." - ); + addWarningAlert(t("alerts.registration.refreshPage")); } } else if (registerTransaction.type === "retirement") { if (!isRegistered) { - addSuccessAlert( - "You have successfully retired from being a DRep!" - ); + addSuccessAlert(t("alerts.retirement.success")); } else { - addWarningAlert( - "You have successfully retired from being a DRep! Please refresh the page." - ); + addWarningAlert(t("alerts.retirement.refreshPage")); } } }); } else { - addSuccessAlert("You have successfully updated DRep metadata!"); + addSuccessAlert(t("alerts.metadataUpdate.success")); } } resetRegisterTransaction(); @@ -370,10 +359,10 @@ function CardanoProvider(props: Props) { if (isEnabled) addErrorAlert( registerTransaction.type === "retirement" - ? "Retirement transaction failed" + ? t("alerts.retirement.failed") : registerTransaction.type === "registration" - ? "Registration transaction failed" - : "Update DRep metadata transaction failed" + ? t("alerts.registration.failed") + : t("alerts.metadataUpdate.failed") ); } }; @@ -396,14 +385,14 @@ function CardanoProvider(props: Props) { ); if (status.transactionConfirmed) { resetVoteTransaction(); - if (isEnabled) addSuccessAlert("You have successfully voted!"); + if (isEnabled) addSuccessAlert(t("alerts.voting.success")); } if ( new Date().getTime() - new Date(voteTransaction?.time).getTime() > TIME_TO_EXPIRE_TRANSACTION ) { resetVoteTransaction(); - if (isEnabled) addErrorAlert("Vote transaction failed"); + if (isEnabled) addErrorAlert(t("alerts.voting.failed")); } }; let interval = setInterval(checkVoteTransaction, REFRESH_TIME); @@ -415,7 +404,7 @@ function CardanoProvider(props: Props) { registerTransaction?.transactionHash || delegateTransaction?.transactionHash) ) { - addWarningAlert("Transaction in progress. Please wait.", 10000); + addWarningAlert(t("alerts.transactionInProgress"), 10000); } }, [delegateTransaction, registerTransaction, voteTransaction]); @@ -528,15 +517,13 @@ function CardanoProvider(props: Props) { try { // Check that this wallet supports CIP-95 connection if (!window.cardano[walletName].supportedExtensions) { - throw new Error("Your wallet does not support CIP-30 extensions."); + throw new Error(t("errors.walletNoCIP30Support")); } else if ( !window.cardano[walletName].supportedExtensions.some( (item) => item.cip === 95 ) ) { - throw new Error( - "Your wallet does not support the required CIP-30 extension, CIP-95." - ); + throw new Error(t("errors.walletNoCIP30Nor90Support")); } // Enable wallet connection const enabledApi = await window.cardano[walletName] @@ -554,18 +541,14 @@ function CardanoProvider(props: Props) { // Check if wallet has enabled the CIP-95 extension const enabledExtensions = await enabledApi.getExtensions(); if (!enabledExtensions.some((item) => item.cip === 95)) { - throw new Error( - "Your wallet did not enable the needed CIP-95 functions during connection." - ); + throw new Error(t("errors.walletNoCIP90FunctionsEnabled")); } const network = await enabledApi.getNetworkId(); if (network != NETWORK) { throw new Error( - `You are trying to connect with a wallet connected to ${ - network == 1 ? "mainnet" : "testnet" - }. Please adjust your wallet settings to connect to ${ - network != 1 ? "mainnet" : "testnet" - } or select a different wallet` + network == 1 + ? t("errors.tryingConnectToMainnet") + : t("errors.tryingConnectToTestnet") ); } setIsMainnet(network == 1); @@ -573,7 +556,7 @@ function CardanoProvider(props: Props) { const usedAddresses = await enabledApi.getUsedAddresses(); const unusedAddresses = await enabledApi.getUnusedAddresses(); if (!usedAddresses.length && !unusedAddresses.length) { - throw new Error("No addresses found."); + throw new Error(t("errors.noAddressesFound")); } if (!usedAddresses.length) { setAddress(unusedAddresses[0]); @@ -603,9 +586,7 @@ function CardanoProvider(props: Props) { .to_hex(); }); } else { - console.log( - "Warning, no registered stake keys, using unregistered stake keys" - ); + console.log(t("warnings.usingUnregisteredStakeKeys")); stakeKeysList = unregisteredStakeKeysList.map((stakeKey) => { const stakeKeyHash = PublicKey.from_hex(stakeKey).hash(); const stakeCredential = Credential.from_keyhash(stakeKeyHash); @@ -647,7 +628,7 @@ function CardanoProvider(props: Props) { const protocol = await getEpochParams(); setItemToLocalStorage(PROTOCOL_PARAMS_KEY, protocol); - return { status: "OK", stakeKey: stakeKeySet }; + return { status: t("ok"), stakeKey: stakeKeySet }; } catch (e) { Sentry.captureException(e); console.error(e); @@ -659,11 +640,11 @@ function CardanoProvider(props: Props) { setIsEnabled(false); throw { status: "ERROR", - error: `${e == undefined ? "Something went wrong" : e}`, + error: `${e == undefined ? t("errors.somethingWentWrong") : e}`, }; } } - throw { status: "ERROR", error: `Something went wrong` }; + throw { status: "ERROR", error: t("errors.somethingWentWrong") }; }, [isEnabled, stakeKeys] ); @@ -739,7 +720,7 @@ function CardanoProvider(props: Props) { const txBuilder = await initTransactionBuilder(); if (!txBuilder) { - throw new Error("Application can not create transaction"); + throw new Error(t("errors.appCannotCreateTransaction")); } if (certBuilder) { @@ -755,7 +736,7 @@ function CardanoProvider(props: Props) { !walletState.usedAddress || !walletApi ) - throw new Error("Check the wallet is connected."); + throw new Error(t("errors.checkIsWalletConnected")); const shelleyOutputAddress = Address.from_bech32( walletState.usedAddress ); @@ -779,7 +760,7 @@ function CardanoProvider(props: Props) { const utxos = await getUtxos(walletApi); if (!utxos) { - throw new Error("Application can not get utxos"); + throw new Error(t("errors.appCannotGetUtxos")); } // Find the available UTXOs in the wallet and use them as Inputs for the transaction const txUnspentOutputs = await getTxUnspentOutputs(utxos); @@ -913,7 +894,7 @@ function CardanoProvider(props: Props) { const certBuilder = CertificatesBuilder.new(); let stakeCred; if (!stakeKey) { - throw new Error("No stake key selected"); + throw new Error(t("errors.noStakeKeySelected")); } // Remove network tag from stake key hash const stakeKeyHash = Ed25519KeyHash.from_hex(stakeKey.substring(2)); @@ -921,7 +902,7 @@ function CardanoProvider(props: Props) { if (registeredStakeKeysListState.length > 0) { stakeCred = Credential.from_keyhash(stakeKeyHash); } else { - console.log("Registering stake key"); + console.log(t("errors.registeringStakeKey")); stakeCred = Credential.from_keyhash(stakeKeyHash); const stakeKeyRegCert = StakeRegistration.new(stakeCred); certBuilder.add(Certificate.new_stake_registration(stakeKeyRegCert)); @@ -984,7 +965,7 @@ function CardanoProvider(props: Props) { anchor ); } else { - console.log("DRep Registration - not using anchor"); + console.log(t("errors.notUsingAnchor")); dRepRegCert = DrepRegistration.new( dRepCred, BigNum.from_str(`${epochParams.drep_deposit}`) @@ -1195,9 +1176,10 @@ function useCardano() { const { openModal, closeModal } = useModal(); const { addSuccessAlert } = useSnackbar(); const navigate = useNavigate(); + const { t } = usei18n(); if (context === undefined) { - throw new Error("useCardano must be used within a CardanoProvider"); + throw new Error(t("errors.useCardano")); } const enable = useCallback( @@ -1210,7 +1192,7 @@ function useCardano() { if (!result.error) { closeModal(); if (result.stakeKey) { - addSuccessAlert(`Wallet connected`, 3000); + addSuccessAlert(t("alerts.walletConnected"), 3000); } if (!isSanchoInfoShown) { openModal({ @@ -1220,23 +1202,26 @@ function useCardano() { dataTestId: "info-about-sancho-net-modal", message: (

- The SanchoNet GovTool is currently in beta and it connects - to{" "} + {t("system.sanchoNetIsBeta")} openInNewTab("https://sancho.network/")} sx={{ cursor: "pointer" }} > - SanchoNet + {t("system.sanchoNet")} .
-
Please note, this tool uses ‘Test ada’ - NOT real ada. All - governance actions and related terms pertain to SanchoNet." +
+ , + ]} + />

), - title: "This tool is connected to SanchoNet", - buttonText: "Ok", + title: t("system.toolConnectedToSanchonet"), + buttonText: t("ok"), }, }); setItemToLocalStorage(SANCHO_INFO_KEY + `_${walletName}`, true); @@ -1255,7 +1240,7 @@ function useCardano() { onSubmit: () => { closeModal(); }, - title: "Oops!", + title: t("modals.common.oops"), dataTestId: "wallet-connection-error-modal", }, }); diff --git a/src/vva-fe/src/hooks/forms/useDelegateTodRepForm.tsx b/src/vva-fe/src/hooks/forms/useDelegateTodRepForm.tsx index e4ff1861b..2bbbb05f8 100644 --- a/src/vva-fe/src/hooks/forms/useDelegateTodRepForm.tsx +++ b/src/vva-fe/src/hooks/forms/useDelegateTodRepForm.tsx @@ -6,6 +6,7 @@ import { PATHS } from "@consts"; import { useCardano, useModal } from "@context"; import { useGetDRepListQuery } from "@hooks"; import { formHexToBech32 } from "@utils"; +import { usei18n } from "@translations"; export interface DelegateTodrepFormValues { dRepId: string; @@ -21,6 +22,7 @@ export const useDelegateTodRepForm = () => { const { openModal, closeModal, modal } = useModal(); const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); + const { t } = usei18n(); const { control, handleSubmit } = useForm(); @@ -43,7 +45,7 @@ export const useDelegateTodRepForm = () => { }); } if (!drepList?.length || !isValidDrep) { - throw new Error("DrepId not found"); + throw new Error(t("errors.dRepIdNotFound")); } const certBuilder = await buildVoteDelegationCert(dRepId); const result = await buildSignSubmitConwayCertTx({ @@ -55,11 +57,10 @@ export const useDelegateTodRepForm = () => { type: "statusModal", state: { status: "success", - title: "Delegation Transaction Submitted!", - message: - "The confirmation of your actual delegation might take a bit of time but you can track it using.", + title: t("modals.delegation.title"), + message: t("modals.delegation.message"), link: "https://adanordic.com/latest_transactions", - buttonText: "Go to dashboard", + buttonText: t("modals.common.goToDashboard"), onSubmit: () => { navigate(PATHS.dashboard); closeModal(); @@ -76,7 +77,7 @@ export const useDelegateTodRepForm = () => { onSubmit: () => { closeModal(); }, - title: "Oops!", + title: t("modals.common.oops"), dataTestId: "delegation-transaction-error-modal", }, }); diff --git a/src/vva-fe/src/hooks/forms/useRegisterAsdRepFormContext.tsx b/src/vva-fe/src/hooks/forms/useRegisterAsdRepFormContext.tsx index a68b89519..d453bf3b5 100644 --- a/src/vva-fe/src/hooks/forms/useRegisterAsdRepFormContext.tsx +++ b/src/vva-fe/src/hooks/forms/useRegisterAsdRepFormContext.tsx @@ -5,12 +5,14 @@ import { useFormContext, useWatch } from "react-hook-form"; import { PATHS } from "@consts"; import { useCardano, useModal } from "@context"; import { UrlAndHashFormValues } from "@hooks"; +import { usei18n } from "@translations"; export const useRegisterAsdRepFormContext = () => { const { buildSignSubmitConwayCertTx, buildDRepRegCert } = useCardano(); const { openModal, closeModal } = useModal(); const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); + const { t } = usei18n(); const { control, @@ -49,11 +51,10 @@ export const useRegisterAsdRepFormContext = () => { type: "statusModal", state: { status: "success", - title: "Registration Transaction Submitted!", - message: - "The confirmation of your registration might take a bit of time but you can track it using.", + title: t("modals.registration.title"), + message: t("modals.registration.message"), link: "https://adanordic.com/latest_transactions", - buttonText: "Go to dashboard", + buttonText: t("modals.common.goToDashboard"), onSubmit: () => { navigate(PATHS.dashboard); closeModal(); @@ -68,9 +69,9 @@ export const useRegisterAsdRepFormContext = () => { type: "statusModal", state: { status: "warning", - title: "Oops!", + title: t("modals.common.oops"), message: errorMessage, - buttonText: "Go to dashboard", + buttonText: t("modals.common.goToDashboard"), onSubmit: () => { navigate(PATHS.dashboard); closeModal(); diff --git a/src/vva-fe/src/hooks/forms/useUpdatedRepMetadataForm.tsx b/src/vva-fe/src/hooks/forms/useUpdatedRepMetadataForm.tsx index da5adae9f..a014cd1f6 100644 --- a/src/vva-fe/src/hooks/forms/useUpdatedRepMetadataForm.tsx +++ b/src/vva-fe/src/hooks/forms/useUpdatedRepMetadataForm.tsx @@ -7,12 +7,14 @@ import { useNavigate } from "react-router-dom"; import { PATHS } from "@consts"; import { useCardano, useSnackbar } from "@context"; +import { usei18n } from "@translations"; export const useUpdatedRepMetadataForm = () => { const { buildSignSubmitConwayCertTx, buildDRepUpdateCert } = useCardano(); const { addSuccessAlert, addErrorAlert } = useSnackbar(); const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); + const { t } = usei18n(); const { handleSubmit, @@ -37,10 +39,10 @@ export const useUpdatedRepMetadataForm = () => { type: "registration", registrationType: "update", }); - if (result) addSuccessAlert("Metadata update submitted"); + if (result) addSuccessAlert(t("alerts.metadataUpdate.success")); navigate(PATHS.dashboard); } catch (e) { - addErrorAlert("Something went wrong while updating metadata"); + addErrorAlert(t("alerts.metadataUpdate.failed")); } finally { setIsLoading(false); } diff --git a/src/vva-fe/src/hooks/forms/useUrlAndHashFormController.tsx b/src/vva-fe/src/hooks/forms/useUrlAndHashFormController.tsx index 437354e8a..2d9305362 100644 --- a/src/vva-fe/src/hooks/forms/useUrlAndHashFormController.tsx +++ b/src/vva-fe/src/hooks/forms/useUrlAndHashFormController.tsx @@ -3,6 +3,7 @@ import { yupResolver } from "@hookform/resolvers/yup"; import { useForm } from "react-hook-form"; import * as Yup from "yup"; import { HASH_REGEX, URL_REGEX } from "@utils"; +import { usei18n } from "@translations"; export interface UrlAndHashFormValues { url?: string; @@ -10,27 +11,37 @@ export interface UrlAndHashFormValues { } export const useUrlAndHashFormController = () => { + const { t } = usei18n(); + const validationSchema = useMemo( () => Yup.object().shape({ url: Yup.string() .trim() - .max(64, "Url must be less than 65 characters") - .test("url-validation", "Invalid URL format", (value) => { - return !value || URL_REGEX.test(value); - }), + .max(64, t("forms.errors.urlTooLong")) + .test( + "url-validation", + t("forms.errors.urlInvalidFormat"), + (value) => { + return !value || URL_REGEX.test(value); + } + ), hash: Yup.string() .trim() .test( "hash-length-validation", - "Hash must be exactly 64 characters long", + t("forms.errors.hashInvalidLength"), (value) => { return !value || value.length === 64; } ) - .test("hash-format-validation", "Invalid hash format", (value) => { - return !value || HASH_REGEX.test(value); - }), + .test( + "hash-format-validation", + t("forms.errors.hashInvalidFormat"), + (value) => { + return !value || HASH_REGEX.test(value); + } + ), }), [] ); diff --git a/src/vva-fe/src/hooks/forms/useVoteActionForm.tsx b/src/vva-fe/src/hooks/forms/useVoteActionForm.tsx index 0dc5f768f..17da0ae4e 100644 --- a/src/vva-fe/src/hooks/forms/useVoteActionForm.tsx +++ b/src/vva-fe/src/hooks/forms/useVoteActionForm.tsx @@ -9,34 +9,45 @@ import { UrlAndHashFormValues } from "./useUrlAndHashFormController"; import { PATHS } from "@consts"; import { useCardano, useSnackbar } from "@context"; import { HASH_REGEX, URL_REGEX } from "@utils"; +import { usei18n } from "@translations"; export interface VoteActionFormValues extends UrlAndHashFormValues { vote: string; } export const useVoteActionFormController = () => { + const { t } = usei18n(); + const validationSchema = useMemo( () => Yup.object().shape({ vote: Yup.string().oneOf(["yes", "no", "abstain"]).required(), url: Yup.string() .trim() - .max(64, "Url must be less than 65 characters") - .test("url-validation", "Invalid URL format", (value) => { - return !value || URL_REGEX.test(value); - }), + .max(64, t("forms.errors.urlTooLong")) + .test( + "url-validation", + t("forms.errors.urlInvalidFormat"), + (value) => { + return !value || URL_REGEX.test(value); + } + ), hash: Yup.string() .trim() .test( "hash-length-validation", - "Hash must be exactly 64 characters long", + t("forms.errors.hashInvalidLength"), (value) => { return !value || value.length === 64; } ) - .test("hash-format-validation", "Invalid hash format", (value) => { - return !value || HASH_REGEX.test(value); - }), + .test( + "hash-format-validation", + t("forms.errors.hashInvalidFormat"), + (value) => { + return !value || HASH_REGEX.test(value); + } + ), }), [] ); diff --git a/src/vva-fe/src/pages/DashboardGovernanceActionsCategory.tsx b/src/vva-fe/src/pages/DashboardGovernanceActionsCategory.tsx index c6b27dadc..bf3cd729f 100644 --- a/src/vva-fe/src/pages/DashboardGovernanceActionsCategory.tsx +++ b/src/vva-fe/src/pages/DashboardGovernanceActionsCategory.tsx @@ -30,6 +30,7 @@ import { getProposalTypeLabel, removeDuplicatedProposals, } from "@utils"; +import { usei18n } from "@translations"; export const DashboardGovernanceActionsCategory = ({}) => { const { category } = useParams(); @@ -39,6 +40,7 @@ export const DashboardGovernanceActionsCategory = ({}) => { const { isMobile, screenWidth } = useScreenDimension(); const navigate = useNavigate(); const { dRep, voteTransaction } = useCardano(); + const { t } = usei18n(); const { data: dRepVotes } = useGetDRepVotesQuery([], ""); @@ -70,7 +72,7 @@ export const DashboardGovernanceActionsCategory = ({}) => { style={{ textDecorationColor: "#0033AD" }} > - Governance Actions + {t("govActions.title")} , @@ -149,7 +151,7 @@ export const DashboardGovernanceActionsCategory = ({}) => { style={{ marginRight: "12px", transform: "rotate(180deg)" }} /> - Back to the list + {t("backToList")} { - Governnance actions with category  + {t("govActions.withCategoryNotExist.partOne")}  {` ${category} `} -  don't exist. + +  {t("govActions.withCategoryNotExist.partTwo")} + ) : ( diff --git a/src/vva-fe/src/pages/DelegateTodRep.tsx b/src/vva-fe/src/pages/DelegateTodRep.tsx index 1c2c20332..f8d44efa4 100644 --- a/src/vva-fe/src/pages/DelegateTodRep.tsx +++ b/src/vva-fe/src/pages/DelegateTodRep.tsx @@ -12,11 +12,13 @@ import { import { useScreenDimension } from "@hooks"; import { WALLET_LS_KEY, getItemFromLocalStorage } from "@/utils/localStorage"; import { useNavigate } from "react-router-dom"; +import { usei18n } from "@translations"; export const DelegateTodRep = () => { const [step, setStep] = useState(1); const { isMobile } = useScreenDimension(); const navigate = useNavigate(); + const { t } = usei18n(); useEffect(() => { if ( @@ -34,7 +36,7 @@ export const DelegateTodRep = () => { imageSRC={ICONS.appLogoIcon} imageWidth={isMobile ? undefined : 42} imageHeight={isMobile ? 24 : 35} - title={"Delegate to DRep"} + title={t("delegation.toDRep.title")} /> @@ -40,19 +42,22 @@ export const ErrorPage = ({ lineHeight={"64px"} sx={{ whiteSpace: "nowrap" }} > - Whoops! + {t("errorPage.whoops")} {state && state.errorCode === 500 - ? "We have an internal server error." + ? t("errorPage.serverError") : errorDescription} - Error {state ? state.errorCode : errorCode} + {t("errorPage.error")} + {state ? state.errorCode : errorCode} {isButton && ( )}
diff --git a/src/vva-fe/src/pages/GovernanceActionDetails.tsx b/src/vva-fe/src/pages/GovernanceActionDetails.tsx index f3be06b8b..d13ecc75b 100644 --- a/src/vva-fe/src/pages/GovernanceActionDetails.tsx +++ b/src/vva-fe/src/pages/GovernanceActionDetails.tsx @@ -20,12 +20,14 @@ import { getItemFromLocalStorage, getShortenedGovActionId, } from "@utils"; +import { usei18n } from "@translations"; export const GovernanceActionDetails = () => { const { state, hash } = useLocation(); const navigate = useNavigate(); const { pagePadding, screenWidth } = useScreenDimension(); const { isEnabled } = useCardano(); + const { t } = usei18n(); const { proposalId } = useParams(); const fullProposalId = proposalId + hash; @@ -50,11 +52,11 @@ export const GovernanceActionDetails = () => { style={{ textDecorationColor: "#0033AD" }} > - Governance Actions + {t("govActions.title")} , - Vote on Governance Action + {t("govActions.voteOnGovActions")} , ]; @@ -77,7 +79,7 @@ export const GovernanceActionDetails = () => { {screenWidth >= 1024 ? ( - Governance Actions + {t("govActions.title")} ) : null} { style={{ marginRight: "12px", transform: "rotate(180deg)" }} /> - Back to the list + {t("backToList")} {isLoading ? ( @@ -159,12 +161,14 @@ export const GovernanceActionDetails = () => { ) : ( - Governnance action with id  + {t("govActions.withIdNotExist.partOne")}  {` ${shortenedGovActionId} `} -  does not exist. + +  {t("govActions.withIdNotExist.partTwo")} + )} diff --git a/src/vva-fe/src/pages/GovernanceActions.tsx b/src/vva-fe/src/pages/GovernanceActions.tsx index 0cc16b470..6f1670970 100644 --- a/src/vva-fe/src/pages/GovernanceActions.tsx +++ b/src/vva-fe/src/pages/GovernanceActions.tsx @@ -9,6 +9,7 @@ import { useScreenDimension } from "@hooks"; import { DataActionsBar } from "@molecules"; import { Footer, TopNav, GovernanceActionsToVote } from "@organisms"; import { WALLET_LS_KEY, getItemFromLocalStorage } from "@utils"; +import { usei18n } from "@translations"; export const GovernanceActions = () => { const [searchText, setSearchText] = useState(""); @@ -19,6 +20,7 @@ export const GovernanceActions = () => { const { isMobile, pagePadding } = useScreenDimension(); const { isEnabled } = useCardano(); const navigate = useNavigate(); + const { t } = usei18n(); useEffect(() => { if (isEnabled && getItemFromLocalStorage(`${WALLET_LS_KEY}_stake_key`)) { @@ -49,7 +51,7 @@ export const GovernanceActions = () => { sx={{ mb: isMobile ? 3.75 : 6, px: pagePadding }} variant={isMobile ? "title1" : "headline3"} > - Governance Actions + {t("govActions.title")} {isMobile && ( { const { category } = useParams(); @@ -37,6 +38,7 @@ export const GovernanceActionsCategory = ({}) => { const { isEnabled } = useCardano(); const navigate = useNavigate(); const { dRep } = useCardano(); + const { t } = usei18n(); const { data: dRepVotes, @@ -72,7 +74,7 @@ export const GovernanceActionsCategory = ({}) => { style={{ textDecorationColor: "#0033AD" }} > - Governance Actions + {t("govActions.title")} , @@ -121,7 +123,7 @@ export const GovernanceActionsCategory = ({}) => { sx={{ mb: isMobile ? 3.75 : 6, px: pagePadding }} variant={isMobile ? "title1" : "headline3"} > - Governance Actions + {t("govActions.title")} {isMobile && ( { style={{ marginRight: "12px", transform: "rotate(180deg)" }} /> - Back to the list + {t("backToList")} { - Governnance actions with category  + {t("govActions.withCategoryNotExist.partOne")}  {category}  {searchText && ( <> - and search phrase  + {t("govActions.withCategoryNotExist.optional")}  {searchText} )} -  don't exist. + +  {t("govActions.withCategoryNotExist.partTwo")} + ) : ( diff --git a/src/vva-fe/src/pages/RegisterAsdRep.tsx b/src/vva-fe/src/pages/RegisterAsdRep.tsx index 8f4e31d93..04c924208 100644 --- a/src/vva-fe/src/pages/RegisterAsdRep.tsx +++ b/src/vva-fe/src/pages/RegisterAsdRep.tsx @@ -16,11 +16,13 @@ import { } from "@hooks"; import { useNavigate } from "react-router-dom"; import { WALLET_LS_KEY, getItemFromLocalStorage } from "@/utils/localStorage"; +import { usei18n } from "@translations"; export const RegisterAsdRep = () => { const [step, setStep] = useState(1); const { isMobile } = useScreenDimension(); const navigate = useNavigate(); + const { t } = usei18n(); const registerAsdRepFormMethods = useRegisterAsdRepFormController(); @@ -40,7 +42,7 @@ export const RegisterAsdRep = () => { imageSRC={ICONS.appLogoIcon} imageWidth={isMobile ? undefined : 42} imageHeight={isMobile ? 24 : 35} - title={"Register as a DRep"} + title={t("registration.registerAsDRep")} /> { const navigate = useNavigate(); @@ -17,6 +18,7 @@ export const UpdatedRepMetadata = () => { } = theme; const { isMobile, pagePadding, screenWidth } = useScreenDimension(); const { isPendingTransaction } = useCardano(); + const { t } = usei18n(); const { submitForm, control, errors, isValid, isLoading } = useUpdatedRepMetadataForm(); @@ -45,7 +47,7 @@ export const UpdatedRepMetadata = () => { }} variant="outlined" > - Cancel + {t("cancel")} ); }, [isMobile]); @@ -66,7 +68,7 @@ export const UpdatedRepMetadata = () => { }} variant="contained" > - Confirm + {t("confirm")} ); }, [isLoading, isMobile, isValid, submitForm]); @@ -78,7 +80,7 @@ export const UpdatedRepMetadata = () => { imageSRC={ICONS.appLogoIcon} imageWidth={isMobile ? undefined : 42} imageHeight={isMobile ? 24 : 35} - title={"Update DRep Metadata"} + title={t("metadataUpdate.title")} /> { sx={{ mt: 1, textAlign: "center" }} variant="headline4" > - Update Information + {t("metadataUpdate.info")} - You can include extra information about yourself by adding a URL - and its hash. + {t("metadataUpdate.description")} { { sx={{ cursor: "pointer" }} > - How to create URL and hash? + {t("forms.howCreateUrlAndHash")} diff --git a/src/vva-fe/src/translations/TypedTrans.tsx b/src/vva-fe/src/translations/TypedTrans.tsx new file mode 100644 index 000000000..361e2edc0 --- /dev/null +++ b/src/vva-fe/src/translations/TypedTrans.tsx @@ -0,0 +1,17 @@ +import React, { ReactElement } from "react"; +import { Trans } from "react-i18next"; +import { TranslationKey } from "./types"; + +interface TypedTransProps { + i18nKey: TranslationKey; + values?: Record; + components?: ReactElement[]; +} + +export const TypedTrans: React.FC = ({ + i18nKey, + values, + components, +}) => { + return ; +}; diff --git a/src/vva-fe/src/translations/i18n.ts b/src/vva-fe/src/translations/i18n.ts new file mode 100644 index 000000000..beaf8a4b6 --- /dev/null +++ b/src/vva-fe/src/translations/i18n.ts @@ -0,0 +1,15 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import { en } from "./locales/en"; + +i18n.use(initReactI18next).init({ + resources: { + en: en, + }, + fallbackLng: "en", + interpolation: { + escapeValue: false, + }, +}); + +export default i18n; diff --git a/src/vva-fe/src/translations/index.ts b/src/vva-fe/src/translations/index.ts new file mode 100644 index 000000000..5753dfbbc --- /dev/null +++ b/src/vva-fe/src/translations/index.ts @@ -0,0 +1,3 @@ +export * from "./TypedTrans"; +export * from "./usei18n"; +export * from "./i18n"; diff --git a/src/vva-fe/src/translations/locales/en.ts b/src/vva-fe/src/translations/locales/en.ts new file mode 100644 index 000000000..57e18ed66 --- /dev/null +++ b/src/vva-fe/src/translations/locales/en.ts @@ -0,0 +1,382 @@ +export const en = { + translation: { + alerts: { + delegation: { + failed: "Delegation transaction failed", + refreshPage: + "Your voting power has been successfully delegated! Please refresh the page.", + success: "Your voting power has been successfully delegated!", + }, + metadataUpdate: { + failed: "Update DRep metadata transaction failed", + success: "You have successfully updated DRep metadata!", + }, + registration: { + failed: "Registration transaction failed", + refreshPage: + "You have successfully registered as a DRep! Please refresh the page.", + success: "You have successfully registered as a DRep!", + }, + retirement: { + failed: "Retirement transaction failed", + refreshPage: + "You have successfully retired from being a DRep! Please refresh the page.", + success: "You have successfully retired from being a DRep!", + }, + voting: { + failed: "Vote transaction failed", + success: "You have successfully voted!", + }, + changesSaved: "Your changes have been saved", + copiedToClipboard: "Copied to clipboard", + transactionInProgress: "Transaction in progress. Please wait.", + walletConnected: "Wallet connected", + }, + dashboard: { + headingOne: "Your Participation", + headingTwo: "See Active Governance Actions", + delegation: { + changeDelegation: "Change delegation", + connectToDelegate: "Connect to delegate", + delegateOwnPower: + "If you want to delegate your own voting power of ₳{{ada}}.", + description: + "If you want to delegate to a DRep or select a default option.", + dRepDelegatedTo: "DRep you delegated to", + toDRep: + "You have delegated your voting power of ₳{{ada}} to a selected DRep.", + toYourself: + "You have delegated your voting power of ₳{{ada}} to yourself.", + useYourVotingPower: "Use your Voting Power", + voteAbstain: + "You have delegated your voting power of ₳{{ada}}. You are going to vote 'ABSTAIN' as default.", + voteNo: + "You have delegated your voting power of ₳{{ada}}. You are going to vote 'NO' as default.", + votingPowerDelegation: "Voting Power Delegation", + yourVotingPowerIsDelegated: + "Your Voting Power is Delegated", + inProgress: { + toDRep: + "Your own voting power of ₳{{ada}} is progress of being delegated. You are going to delegate your voting power to a selected DRep.", + toYourself: + "Your own voting power of ₳{{ada}} is in progress of being delegated. You are going to delegate your voting power to yourself.", + voteAbstain: + "Your own voting power of ₳{{ada}} is in progress of being delegated. You are going to vote ‘ABSTAIN’ as default.", + voteNo: + "Your own voting power of ₳{{ada}} is in progress of being delegated. You are going to vote ‘NO’ as default.", + }, + }, + govActions: { + description: "Review governance actions submitted on-chain.", + reviewAndVote: "Review and vote", + title: "Governance Actions", + view: "View governance actions", + }, + registration: { + changeMetadata: "Change metadata", + connectToRegister: "Connect to register", + dRepRegistration: "DRep Registration", + dRepRetirement: "DRep Retirement", + dRepUpdate: "DRep Update", + description: + "If you want to directly participate in voting and have other ada holders delegate their voting power to you.", + holdersCanDelegate: + "Ada holders can delegate their voting power to you.", + ifYouWant: + "If you want to directly participate in voting and have other ada holders delegate their voting power to you.", + metadataUpdateInProgress: + "The update DRep metadata is ongoing. This may take several minutes.", + register: "Register", + registerAgain: "Register Again as a dRep", + registerAsDRep: "Register as a DRep", + registrationInProgress: + "The registration process is ongoing. This may take several minutes.", + retire: "Retire as a DRep", + retirementInProgress: + "The retirement process is ongoing. This may take several minutes.", + youAreRegistered: "You are Registered as a DRep", + }, + }, + delegation: { + description: + "You can delegate your voting power to a DRep or to a pre-defined voting option.", + dRepIdDescription: "The DRep ID is the identifier of a DRep.", + heading: "Use your Voting Power", + otherOptions: "Other options", + pasteDRepId: "Paste DRep ID", + votingPowerToDelegate: "Voting power to delegate:", + whereFindDRepId: "The DRep ID is the identifier of a DRep.", + abstain: { + subtitle: "Select this to vote ABSTAIN to every vote.", + title: "Vote ABSTAIN as default", + }, + noConfidence: { + subtitle: + "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no-confidence proposals.", + title: "Signal no confidence", + }, + toDRep: { + subtitle: + "Select this to delegate to a DRep using their related DRep ID.", + title: "Delegate to DRep", + }, + toMyself: { + subtitle: "Select this to delegate your own voting power to yourself.", + title: "Delegate to myself", + }, + }, + errorPage: { + backToDashboard: "Back to dashboard", + backToHomepage: "Back to homepage", + error: "Error ", + serverError: "We have an internal server error.", + whoops: "Whoops!", + }, + errors: { + appCannotCreateTransaction: "Application can not create transaction.", + appCannotGetUtxos: "Application can not get utxos", + checkIsWalletConnected: "Check if the wallet is connected.", + dRepIdNotFound: "DrepId not found", + notUsingAnchor: "DRep Registration - not using anchor", + noAddressesFound: "No addresses found", + noStakeKeySelected: "No stake key selected", + registeringStakeKey: "Registering stake key", + somethingWentWrong: "Something went wrong", + useCardano: "useCardano must be used within a CardanoProvider", + tryingConnectToMainnet: + "You are trying to connect with a wallet connected to mainnet. Please adjust your wallet settings to connect to mainnet or select a different wallet.", + tryingConnectToTestnet: + "You are trying to connect with a wallet connected to testnet. Please adjust your wallet settings to connect to testnet or select a different wallet.", + walletNoCIP30Nor90Support: + "Your wallet does not support the required CIP-30 extension, CIP-95.", + walletNoCIP30Support: "Your wallet does not support CIP-30 extensions.", + walletNoCIP90FunctionsEnabled: + "Your wallet did not enable the needed CIP-95 functions during connection.", + }, + footer: { + copyright: "© 2023 Voltaire Gov Tool", + privacyPolicy: "Privacy policy", + }, + forms: { + hashPlaceholder: "The hash of your URL", + howCreateUrlAndHash: "How to create URL and hash?", + urlWithContextPlaceholder: "Your URL with with your context", + urlWithInfoPlaceholder: "Your URL with extra info about you", + errors: { + hashInvalidFormat: "Invalid hash format", + hashInvalidLength: "Hash must be exactly 64 characters long", + urlTooLong: "Url must be less than 65 characters", + urlInvalidFormat: "Invalid URL format", + }, + }, + govActions: { + changeVote: "Change vote", + changeYourVote: "Change your vote", + chooseHowToVote: "Choose how you want to vote:", + details: "Governance Details:", + expiryDate: "Expiry date:", + filterTitle: "Governance Action Type", + forGovAction: "for this Governance Action", + id: "Governance Action ID:", + myVote: "My Vote:", + noResults: "No results for the search.", + optional: "(optional)", + provideContext: "Provide context about your vote", + selectDifferentOption: "Select a different option to change your vote", + showVotes: "Show votes", + submissionDate: "Submission date:", + title: "Governance Actions", + toVote: "To vote", + type: "Governance Action Type:", + viewOtherDetails: "View other details", + viewProposalDetails: "View proposal details", + vote: "Vote", + voted: "Voted", + voteOnGovActions: "Vote on Governance Action", + voteSubmitted: "Vote submitted", + voteTransaction: "Vote transaction", + votes: "Votes:", + votesSubmitted: "Votes submitted", + votesSubmittedOnChain: + "Votes submitted on-chain by DReps, SPOs and Constitutional Committee members.", + youHaventVotedYet: + "You haven't voted on any Governance Actions yet. Check the 'To vote on' section to vote on Governance Actions.", + withCategoryNotExist: { + partOne: "Governnance actions with category", + optional: "and search phrase", + partTwo: "don't exist.", + }, + withIdNotExist: { + partOne: "Governance action with id", + partTwo: "does not exist.", + }, + }, + hero: { + connectWallet: "Connect your wallet", + description: + "Interact with SanchoNet using GovTool - a friendly user\ninterface connected to SanchoNet. You can delegate\nyour voting power (tAda) or become a SanchoNet DRep\nto allow people to delegate voting power to you.", + headline: "SanchoNet \n Governance Tool", + }, + menu: { + faqs: "FAQs", + guides: "Guides", + help: "Help", + myDashboard: "My Dashboard", + viewGovActions: "View Governance Actions", + }, + metadataUpdate: { + description: + "You can include extra information about yourself by adding a URL and its hash.", + info: "Update Information", + title: "Update DRep Metadata", + }, + modals: { + common: { + goToDashboard: "Go to Dashboard", + oops: "Oops!", + }, + delegation: { + message: + "The confirmation of your actual delegation might take a bit of time but you can track it using", + title: "Delegation Transaction Submitted!", + }, + externalLink: { + beCareful: "Be Careful!", + continueTo: "Continue to external link", + description: + "Exercise caution and verify the website's authenticity before sharing personal information. To proceed, click 'Continue'. To stay on Voltaire, click 'Cancel'.", + safety: "External Link Safety", + thisIs: "This is an external link:", + youAreAboutToOpen: "You are about to open an external link to:", + }, + registration: { + message: + "The confirmation of your registration might take a bit of time but you can track it using", + title: "Registration Transaction Submitted!", + }, + retirement: { + message: + "The confirmation of your retirement might take a bit of time but you can track it using", + title: "Retirement Transaction Submitted!", + }, + votingPower: { + govActionsVotes: "Governance Action votes", + votesSubmittedByDReps: "Votes submitted by DReps", + yourVote: "Your vote", + }, + waitForTransaction: { + title: "Please wait for your previous transaction to be completed.", + message: + "Before performing a new action please wait for the previous action transaction to be completed.", + }, + }, + registration: { + descriptionStepOne: + "You can include extra information about yourself by adding a URL and its hash.", + descriptionStepTwo: + "By clicking register you create your DRep ID within your wallet and become a DRep.\n\nOnce the registration has completed your DRep ID will be shown on your dashboard. You will be able to share your DRep ID so that other ada holders can delegate their voting power to you.", + headingStepOne: "Add Information", + headingStepTwo: "Confirm DRep registration", + optional: "OPTIONAL", + register: "Register", + registerAsDRep: "Register as a DRep", + }, + slider: { + showAll: "Show all", + viewAll: "View all", + }, + system: { + sanchoNet: "SanchoNet", + sanchoNetIsBeta: + "The SanchoNet GovTool is currently in beta and it connects to ", + testAdaNote: + "Please note, this tool uses ‘Test ada’ <0>NOT real ada. All governance actions and related terms pertain to SanchoNet.", + toolConnectedToSanchonet: "This tool is connected to SanchoNet", + }, + tooltips: { + delegateTodRep: { + abstain: { + heading: "Abstaining", + paragraphOne: + "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no-confidence proposals.", + }, + noConfidence: { + heading: "No confidence", + paragraphOne: + "If you don’t have trust in the current constitutional committee you signal ‘No-confidence’. By voting ‘No’ means you don’t want governance actions to be ratified.", + }, + todRep: { + heading: "Delegation to DRep", + paragraphOne: + "DReps are representatives of the ada holders that can vote on governance actions.", + }, + toMyself: { + heading: "Delegate to myself", + paragraphOne: + "If you are registered as DRep you can delegate your voting power on yourself.", + }, + }, + expiryDate: { + heading: "Expiry Date", + paragraphOne: + "The date when the governance action will expiry if it doesn’t reach ratification thresholds.", + paragraphTwo: + "IMPORTANT: If the governance action is ratified before the expiry date it will be considered ratified and it will not be available to vote on afterwards.", + }, + submissionDate: { + heading: "Submission Date", + paragraphOne: + "The date when the governance action was submitted on-chain.", + }, + votingPower: { + heading: "DRep Voting Power", + paragraphOne: + "This is the voting power delegated to you as a DRep and it is calculated at the end of every epoch for the epoch that just ended.", + paragraphTwo: + "IMPORTANT: When voting, the voting power provides an indication and not the exact number.", + }, + }, + wallet: { + cantSeeWalletQuestion: + "Can’t see your wallet? Check what wallets are currently compatible with GovTool ", + chooseWallet: "Choose the wallet you want to connect with:", + connect: "Connect", + connectWallet: "Connect wallet", + connectYourWallet: "Connect your Wallet", + connectYourWalletButton: "Connect your wallet", + connectedWallet: "Connected Wallet:", + disconnect: "Disconnect", + noWalletsToConnect: + "You don't have wallets to connect, install a wallet and refresh the page and try again", + pickStakeKey: "Pick Stake Key", + selectStakeKey: "Select the stake key you want to use:", + }, + warnings: { + usingUnregisteredStakeKeys: + "Warning, no registered stake keys, using unregistered stake keys", + }, + abstain: "Abstain", + back: "Back", + backToList: "Back to the list", + cancel: "Cancel", + clear: "Clear", + confirm: "Confirm", + continue: "Continue", + delegate: "Delegate", + here: "here", + inProgress: "In progress", + learnMore: "Learn more", + loading: "Loading...", + myDRepId: "My DRep ID:", + nextStep: "Next step", + no: "No", + ok: "Ok", + select: "Select", + seeTransaction: "See transaction", + skip: "Skip", + sortBy: "Sort by", + thisLink: "this link", + votingPower: "Voting power:", + yes: "Yes", + }, +}; diff --git a/src/vva-fe/src/translations/types.ts b/src/vva-fe/src/translations/types.ts new file mode 100644 index 000000000..5b7879590 --- /dev/null +++ b/src/vva-fe/src/translations/types.ts @@ -0,0 +1,26 @@ +import { en } from "./locales/en"; + +type JoinStringsWithDotNotation< + S1 extends string, + S2 extends string +> = `${S1}${"" extends S2 ? "" : "."}${S2}`; + +type DeepKeys = ObjectToParse extends Record + ? { + [Key in keyof ObjectToParse]: JoinStringsWithDotNotation< + Key & string, + DeepKeys + >; + }[keyof ObjectToParse] + : ""; + +type DeepestKeys = ObjectToParse extends Record + ? { + [Key in keyof ObjectToParse]: JoinStringsWithDotNotation< + Key & string, + DeepKeys + >; + }[keyof ObjectToParse] + : ""; + +export type TranslationKey = DeepestKeys<(typeof en)["translation"]>; diff --git a/src/vva-fe/src/translations/usei18n.ts b/src/vva-fe/src/translations/usei18n.ts new file mode 100644 index 000000000..dfed73515 --- /dev/null +++ b/src/vva-fe/src/translations/usei18n.ts @@ -0,0 +1,15 @@ +import { useTranslation } from "react-i18next"; +import { TranslationKey } from "./types"; + +interface InterpolationValues { + [key: string]: number | string | undefined; +} + +export const usei18n = () => { + const { t: i18nT } = useTranslation(); + + const t = (key: TranslationKey, interpolation?: InterpolationValues) => + i18nT(key, interpolation); + + return { t }; +}; diff --git a/src/vva-fe/tsconfig.json b/src/vva-fe/tsconfig.json index bbf7df437..e178550cf 100644 --- a/src/vva-fe/tsconfig.json +++ b/src/vva-fe/tsconfig.json @@ -33,7 +33,8 @@ "@organisms": ["./src/components/organisms/index.ts"], "@context": ["./src/context/index.ts"], "@models": ["./src/models/index.ts"], - "@utils": ["./src/utils/index.ts"] + "@utils": ["./src/utils/index.ts"], + "@translations": ["./src/translations/index.ts"] } }, "include": ["src"], diff --git a/src/vva-fe/vite.config.ts b/src/vva-fe/vite.config.ts index 4fb054b4e..a5f9ca59a 100644 --- a/src/vva-fe/vite.config.ts +++ b/src/vva-fe/vite.config.ts @@ -41,6 +41,10 @@ export default defineConfig({ find: "@utils", replacement: path.resolve(__dirname, "./src/utils"), }, + { + find: "@translations", + replacement: path.resolve(__dirname, "./src/translations"), + }, ], }, }); diff --git a/src/vva-fe/yarn.lock b/src/vva-fe/yarn.lock index 2be4ff375..67d6a2974 100644 --- a/src/vva-fe/yarn.lock +++ b/src/vva-fe/yarn.lock @@ -1016,6 +1016,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650" + integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1502,7 +1509,7 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== -"@istanbuljs/load-nyc-config@^1.0.0": +"@istanbuljs/load-nyc-config@^1.0.0", "@istanbuljs/load-nyc-config@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== @@ -1777,6 +1784,17 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsdevtools/coverage-istanbul-loader@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz#2a4bc65d0271df8d4435982db4af35d81754ee26" + integrity sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA== + dependencies: + convert-source-map "^1.7.0" + istanbul-lib-instrument "^4.0.3" + loader-utils "^2.0.0" + merge-source-map "^1.1.0" + schema-utils "^2.7.0" + "@juggle/resize-observer@^3.3.1": version "3.4.0" resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" @@ -2366,6 +2384,21 @@ lodash "^4.17.21" ts-dedent "^2.0.0" +"@storybook/addon-coverage@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@storybook/addon-coverage/-/addon-coverage-1.0.0.tgz#b3c587f240a53500a72a5f8c9a30495fac410877" + integrity sha512-7RMllRtDwyWH2fOeCS7ZG8o5eDTd44C0ZwA+m7dJDrnOXled75RddBPMYF+BsJjeAAWOkWxsII05EKCgS7QG2A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.1.0" + "@jsdevtools/coverage-istanbul-loader" "^3.0.5" + "@types/istanbul-lib-coverage" "^2.0.4" + convert-source-map "^2.0.0" + espree "^9.6.1" + istanbul-lib-instrument "^6.0.1" + source-map "^0.7.4" + test-exclude "^6.0.0" + vite-plugin-istanbul "^3.0.1" + "@storybook/addon-docs@7.6.3": version "7.6.3" resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-7.6.3.tgz#113e62a13729489e2657d2b779b5be33893f6758" @@ -3278,7 +3311,7 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.4": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== @@ -3305,7 +3338,7 @@ jest-matcher-utils "^28.0.0" pretty-format "^28.0.0" -"@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -3680,6 +3713,11 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -3986,6 +4024,11 @@ big-integer@^1.6.16, big-integer@^1.6.44: resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -4240,6 +4283,11 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chromatic@^10.0.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-10.5.0.tgz#85349cccb30d2d004668fdd33314f701300fc72e" + integrity sha512-fOdncJpF/JxRKhNyKwkxw08tVyqF+SJq/TI9cEEUgqip/+KHDfW4U0fy2U1C+RXuJefN6FgqOflsEUoURNBmGg== + ci-info@^3.2.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" @@ -4843,6 +4891,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -5807,6 +5860,13 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + html-tags@^3.1.0: version "3.3.1" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" @@ -5856,6 +5916,13 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +i18next@^23.7.19: + version "23.7.19" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.7.19.tgz#037ac683610417178b881355e5cdc38380c0ca17" + integrity sha512-1aP+YSJl+nLxr42ZJtNhpWpNWYsc6nCbVCf2x4uizIX1BYfcigiRMlb3vOkE1p3+qrI+aD6h5G2Fg+2d2oMIOQ== + dependencies: + "@babel/runtime" "^7.23.2" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -6230,7 +6297,7 @@ istanbul-lib-hook@^3.0.0: dependencies: append-transform "^2.0.0" -istanbul-lib-instrument@^4.0.0: +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== @@ -6240,7 +6307,7 @@ istanbul-lib-instrument@^4.0.0: istanbul-lib-coverage "^3.0.0" semver "^6.3.0" -istanbul-lib-instrument@^5.0.4: +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== @@ -6251,7 +6318,7 @@ istanbul-lib-instrument@^5.0.4: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" -istanbul-lib-instrument@^6.0.0: +istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== @@ -6860,7 +6927,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.2.3: +json5@^2.1.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -6928,6 +6995,15 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -7104,6 +7180,13 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-source-map@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" + integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw== + dependencies: + source-map "^0.6.1" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -7972,6 +8055,14 @@ react-hook-form@^7.47.0: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.48.2.tgz#01150354d2be61412ff56a030b62a119283b9935" integrity sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A== +react-i18next@^14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.0.1.tgz#75351b25be076ad7391360b5a111b59ca87e0b63" + integrity sha512-TMV8hFismBmpMdIehoFHin/okfvgjFhp723RYgIqB4XyhDobVMyukyM3Z8wtTRmajyFMZrBl/OaaXF2P6WjUAw== + dependencies: + "@babel/runtime" "^7.22.5" + html-parse-stringify "^3.0.1" + react-is@18.1.0: version "18.1.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67" @@ -8357,6 +8448,15 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +schema-utils@^2.7.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + "semver@2 || 3 || 4 || 5", semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" @@ -8530,6 +8630,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -9176,6 +9281,16 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +vite-plugin-istanbul@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/vite-plugin-istanbul/-/vite-plugin-istanbul-3.0.4.tgz#a8367e393fc51c9e071607de5bacf46ddcac698f" + integrity sha512-DJy3cq6yOFbsM3gLQf/3zeuaJNJsfBv5dLFdZdv8sUV30xLtZI+66QeYfHUyP/5vBUYyLA+xNUCSG5uHY6w+5g== + dependencies: + "@istanbuljs/load-nyc-config" "^1.1.0" + istanbul-lib-instrument "^5.1.0" + picocolors "^1.0.0" + test-exclude "^6.0.0" + vite@^4.3.9: version "4.5.1" resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.1.tgz#3370986e1ed5dbabbf35a6c2e1fb1e18555b968a" @@ -9187,6 +9302,11 @@ vite@^4.3.9: optionalDependencies: fsevents "~2.3.2" +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + wait-on@^7.0.0: version "7.2.0" resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.2.0.tgz#d76b20ed3fc1e2bebc051fae5c1ff93be7892928"