diff --git a/apps/shell/.tolgeerc.js b/apps/shell/.tolgeerc.js new file mode 100644 index 000000000..ddb766a38 --- /dev/null +++ b/apps/shell/.tolgeerc.js @@ -0,0 +1,41 @@ +const AVAILABLE_LOCALES = ['en', 'ar', 'id', 'ru', 'tr']; +const ALL_NAMESPACES = [ + 'authz', + 'common', + 'faucet', + 'governance', + 'main', + 'staking', + 'uc-dao', + 'utils', +]; + +module.exports = { + $schema: 'https://tolgee.io/cli-schema.json', + projectId: 3, + apiKey: process.env.TOLGEE_API_KEY, + apiUrl: process.env.TOLGEE_API_URL, + format: 'JSON_TOLGEE', + patterns: ['../../libs/**/*.ts?(x)', '../../apps/shell/src/**/*.ts?(x)'], + defaultNamespace: 'common', + parser: 'react', + push: { + files: AVAILABLE_LOCALES.flatMap((locale) => { + return ALL_NAMESPACES.map((namespace) => { + return { + path: `./messages/${namespace}/${locale}.json`, + language: locale, + namespace, + }; + }); + }), + forceMode: 'OVERRIDE', + tagNewKeys: ['development'], + }, + pull: { + path: './messages', + languages: AVAILABLE_LOCALES, + namespaces: ALL_NAMESPACES, + fileStructureTemplate: '{namespace}/{languageTag}.{extension}', + }, +}; diff --git a/apps/shell/messages/ar.json b/apps/shell/messages/ar.json deleted file mode 100644 index 51be30cee..000000000 --- a/apps/shell/messages/ar.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "hello-world": "مرحبا بالعالم!" -} diff --git a/apps/shell/messages/authz/en.json b/apps/shell/messages/authz/en.json new file mode 100644 index 000000000..dc9c0777b --- /dev/null +++ b/apps/shell/messages/authz/en.json @@ -0,0 +1,29 @@ +{ + "access-granted-to-you": "Access you have been granted", + "access-you-granted": "Access you have granted", + "five-years": "5 Years", + "grant-access": "Grant access", + "grant-loading": "Grant in progress", + "grant-period": "Grant period", + "grant-success": "Grant successful", + "grant-type": "Grant type", + "grantee": "Grantee", + "grantee-address": "Grantee address", + "granter": "Granter", + "hundred-years": "100 Years", + "invalid-grantee-wallet-message": "You should enter valid grantee wallet to see info", + "memo": "Memo", + "message": "Message", + "one-month": "1 Month", + "one-week": "1 Week", + "one-year": "1 Year", + "revoke-button": "Revoke", + "revoke-loading": "Revoke in progress", + "revoke-success": "Revoke successful", + "selected-grantee": "Selected grantee", + "six-months": "6 Months", + "submit-proposal": "Submit Proposal", + "three-months": "3 Months", + "valid-thru": "Valid thru", + "vote": "Vote" +} diff --git a/apps/shell/messages/authz/ru.json b/apps/shell/messages/authz/ru.json new file mode 100644 index 000000000..59fff290f --- /dev/null +++ b/apps/shell/messages/authz/ru.json @@ -0,0 +1,12 @@ +{ + "access-you-granted" : "Доступ, который вы предоставили", + "five-years" : "5 лет", + "hundred-years" : "100 лет", + "one-month" : "1 месяц", + "one-week" : "1 неделя", + "one-year" : "1 год", + "revoke-button" : "Отозвать", + "revoke-loading" : "Отзыв в процессе", + "six-months" : "6 месяцев", + "three-months" : "3 месяца" +} \ No newline at end of file diff --git a/apps/shell/messages/common/en.json b/apps/shell/messages/common/en.json new file mode 100644 index 000000000..e58b14c7f --- /dev/null +++ b/apps/shell/messages/common/en.json @@ -0,0 +1,114 @@ +{ + "active-status": "Active", + "add-memo": "Add memo", + "add-your-memo": "Add your memo", + "address": "Address", + "address-conversion-title": "Address Conversion", + "all-rights-reserved": ". All rights reserved", + "amount-error-min": "Bellow minimal value", + "amount-error-more-than-have": "More than you have", + "authz": "Authz", + "available": "Available", + "balance": "Balance", + "cancel-button": "Cancel", + "claim-all-rewards": "Claim all rewards", + "click-to-copy-value": "Click to copy {value}", + "complete-captcha-message": "Please complete the captcha to continue", + "complete-captcha-title": "Complete the captcha", + "confirm-button": "Confirm", + "confirm-delegation": "Confirm delegation", + "connect-wallet-button": "Connect wallet", + "connect-wallet-message": "You should connect wallet first", + "continue-button": "Continue", + "copied": "Copied!", + "dao-amount": "UnitedContributorsDAO: {amount}", + "days": "Days", + "delegate": "Delegate", + "deposit": "Deposit", + "deposit-end": "Deposit end", + "deposit-period": "Deposit Period", + "deposit-total-from-min": "{totalDeposit} {symbol} from {minDeposit} {symbol}", + "description": "Description", + "disconnect": "Disconnect", + "enter-amount": "Enter Amount", + "enter-text": "Enter text", + "failed": "Failed", + "faucet": "Faucet", + "fee": "Fee", + "fetching-proposals": "Fetching proposals", + "go-back": "Go back", + "governance": "Governance", + "grant-access": "Grant access", + "grant-loading": "Grant in progress", + "grant-period": "Grant period", + "grant-success": "Grant successful", + "grant-type": "Grant type", + "grantee-address": "Grantee address", + "haqq-network": "HAQQ Network", + "hide-info": "Hide Info", + "hours": "Hours", + "inactive-status": "Inactive", + "info": "Info", + "invalid-grantee-wallet-message": "You should enter valid grantee wallet to see info", + "jailed-status": "Jailed", + "liquid-staked-amount": "Liquid Staked: {amount}", + "low-balance-message": "Not enough balance for the commission fee. Transfer funds to your account to proceed.", + "low-balance-title": "Low balance", + "max": "Max", + "memo": "Memo", + "min-amount-to-claim-rewards": "Minimum amount to claim rewards is {amount} ISLM", + "minutes-shortened": "Min", + "my-account": "My account", + "my-balance": "My balance", + "my-delegations": "My delegations", + "name": "Name", + "nothing-found": "Nothing found", + "open-in-haqq-wallet": "Open in HAQQ Wallet", + "page-not-found": "Page not found", + "passed": "Passed", + "redelegate": "Redelegate", + "rejected": "Rejected", + "rewards": "Rewards", + "scan-with-haqq-wallet": "Scan with HAQQ Wallet", + "search-by-name": "Search by name", + "seconds-shortened": "Sec", + "select-network": "Select network", + "select-wallet-heading": "Select wallet", + "selected-grantee": "Selected grantee", + "send-button": "Send", + "show-info": "Show Info", + "something-went-wrong": "Something went wrong!", + "staked": "Staked", + "staked-amount": "Staked: {amount}", + "staking": "Staking", + "status": "Status", + "testnet-banner": "You are on test network", + "to-the-address": " - to the address {address} ?", + "tools": "Tools", + "top-validators-warning-message": "You are attempting to delegate to a validator that ranks in the\n top {votingPowerPercent}% by voting power. Delegating to highly\n ranked validators might centralize voting power and potentially\n reduce the network's decentralization. Please ensure you\n understand the implications before proceeding.", + "total-deposit": "Total deposit", + "try-again": "Try again", + "uc-dao": "UC DAO", + "unbonding": "Unbonding", + "unbonding-amount": "Unbonding: {amount}", + "undelegate": "Undelegate", + "unsupported-network-message": "Your current action cannot be performed as the application is connected to an unsupported network. Please select one of the supported networks from the list below to proceed.", + "unsupported-network-title": "Unsupported
Network", + "utils": "Utils", + "version": "version: ", + "vested-amount": "Vested: {amount}", + "vote-option-abstain": "Abstain", + "vote-option-no": "No", + "vote-option-no-with-veto": "No with veto", + "vote-option-yes": "Yes", + "voting": "Voting", + "voting-end": "Voting end", + "voting-power": "Voting power", + "voting-results": "Voting results", + "voting-start": "Voting Start", + "voting-status": "Voting status", + "wallet-connect": "WalletConnect", + "warning": "Warning", + "you-deposited": "You Deposited: {amount}", + "you-voted": "You voted:" +} diff --git a/apps/shell/messages/common/ru.json b/apps/shell/messages/common/ru.json new file mode 100644 index 000000000..d0eab4aa4 --- /dev/null +++ b/apps/shell/messages/common/ru.json @@ -0,0 +1,19 @@ +{ + "address": "Адрес", + "balance": "Баланс", + "cancel-button": "Отмена", + "click-to-copy-value": "Нажмите, чтобы скопировать {value}", + "connect-wallet-button": "Подключить кошелек", + "copied": "Скопировано!", + "description": "Описание", + "disconnect": "Отключить", + "min-amount-to-claim-rewards": "Минимальная сумма для получения вознаграждения составляет {amount} ISLM", + "my-balance": "Мой баланс", + "my-delegations": "Мои делегации", + "nothing-found": "Ничего не найдено", + "page-not-found": "Страница не найдена", + "rewards": "Награды", + "send-button": "Отправить", + "status": "Статус", + "try-again": "Попробуйте еще раз" +} diff --git a/apps/shell/messages/en.json b/apps/shell/messages/en.json deleted file mode 100644 index 2d0fed550..000000000 --- a/apps/shell/messages/en.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "hello-world": "Hello, World!" -} diff --git a/apps/shell/messages/faucet/en.json b/apps/shell/messages/faucet/en.json new file mode 100644 index 000000000..2e34a33de --- /dev/null +++ b/apps/shell/messages/faucet/en.json @@ -0,0 +1,11 @@ +{ + "claim-tokens" : "Claim tokens", + "faucet" : "Faucet", + "github" : "Github", + "login-github" : "Login with github", + "next-token-request-available" : "Next request tokens available after", + "request-tokens" : "Request tokens", + "switch-chain" : "Switch to {chain}", + "tokens-claimed" : "Tokens claimed", + "wallet" : "Wallet" +} \ No newline at end of file diff --git a/apps/shell/messages/faucet/ru.json b/apps/shell/messages/faucet/ru.json new file mode 100644 index 000000000..058201131 --- /dev/null +++ b/apps/shell/messages/faucet/ru.json @@ -0,0 +1,6 @@ +{ + "faucet": "Кран", + "login-github": "Войти через github", + "request-tokens": "Запросить токены", + "switch-chain": "Переключиться на {chain}" +} diff --git a/apps/shell/messages/governance/en.json b/apps/shell/messages/governance/en.json new file mode 100644 index 000000000..9c0c88e1d --- /dev/null +++ b/apps/shell/messages/governance/en.json @@ -0,0 +1,30 @@ +{ + "cast-vote": "Cast your vote", + "change-vote-message": "You can change your vote while the voting is in progress", + "created-at": "Created at (GMT)", + "dates": "Dates", + "deposit-end-gmt": "Deposit end (GMT)", + "enter-deposit-message": "Enter the amount you want to deposit", + "fetching-proposal-details": "Fetching proposal details", + "parameter-changes": "Parameter changes", + "proposal-deposit-alert": "If the proposal does not collect the required number of deposits in a certain time, it will reject", + "proposal-types": { + "cancel-software-upgrade": "Cancel software upgrade", + "client-update": "Client update", + "parameter-change": "Parameter change", + "register-coin": "Register coin", + "register-erc20": "Register ERC20", + "software-upgrade": "Software upgrade", + "text": "Text" + }, + "quorum": "Quorum", + "show-all-dates": "Show all dates", + "turnout": "Turnout", + "upgrade-plan": "Upgrade plan", + "vote-end": "Vote end (GMT)", + "vote-fail-error": "For some reason your vote failed.", + "vote-in-progress": "Vote in progress", + "vote-start": "Vote start (GMT)", + "vote-will-count": "Your vote will be counted!!!", + "your-balance": "Your balance: {balance} {symbol}" +} diff --git a/apps/shell/messages/governance/ru.json b/apps/shell/messages/governance/ru.json new file mode 100644 index 000000000..91d951a17 --- /dev/null +++ b/apps/shell/messages/governance/ru.json @@ -0,0 +1,5 @@ +{ + "vote-in-progress": "Голосование в процессе", + "vote-will-count": "Ваш голос будет учтен!!!", + "your-balance": "Ваш баланс: {balance} {symbol}" +} diff --git a/apps/shell/messages/id.json b/apps/shell/messages/id.json deleted file mode 100644 index 6b8e4db14..000000000 --- a/apps/shell/messages/id.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "hello-world": "Halo Dunia!" -} diff --git a/apps/shell/messages/main/en.json b/apps/shell/messages/main/en.json new file mode 100644 index 000000000..2cdf7b237 --- /dev/null +++ b/apps/shell/messages/main/en.json @@ -0,0 +1,19 @@ +{ + "accounts" : "Accounts", + "active-out-of-total" : "{active} out of {total}", + "active-validators" : "Active validators", + "available-for-stake" : "Available: {amount}", + "available-staking" : "Available for staking", + "fetching-validators" : "Fetching validators list", + "latest-proposals" : "Latest proposals", + "link-to-governance" : "Go to Governance", + "link-to-staking" : "Go to Staking", + "liquid-staked" : "Liquid staked", + "locked" : "Locked: {amount}", + "no-delegations" : "You don't have any active delegations", + "regular-staked" : "Regular staked", + "shell-title" : "Shell", + "staking-balance-popup-message" : "In regular staking you can use coins in liquid staking", + "total-staked" : "Total staked", + "total-supply" : "Total supply" +} \ No newline at end of file diff --git a/apps/shell/messages/staking/en.json b/apps/shell/messages/staking/en.json new file mode 100644 index 000000000..b5aa035a0 --- /dev/null +++ b/apps/shell/messages/staking/en.json @@ -0,0 +1,59 @@ +{ + "amount-error-more-than-delegation": "More than your delegation", + "annualized-yield": "Annual percentage yield", + "apy": "APY", + "attention-withdrawal-warning": "{count, plural, other {Attention! If in the future you want to withdraw the staked funds, it will take {count} days}}", + "commission": "Commission", + "confirm-redelegation": "Confirm redelegation", + "confirm-undelegation": "Confirm undelegation", + "current": "Current", + "delegation-declined": "Delegation declined", + "delegation-progress": "Delegation in progress", + "delegation-success": "Delegation successful", + "email": "E-mail", + "estimated-fee": "Estimated fee", + "explorer-link": "Explorer link", + "fee-asc": "By fee (a-z)", + "fee-desc": "By fee (z-a)", + "fetching-validators-message": "Fetching validators list", + "funds-undelegated-in-days": "{count, plural, one {The funds will be undelegated within # day} other {The funds will be undelegated within # days}}", + "get-my-rewards": "Get my rewards", + "get-rewards": "Get rewards", + "liquid-staking": "Liquid staking", + "max": "Max", + "max-change": "Max Change", + "memo-placeholder": "Add your memo", + "my-delegation": "My delegation", + "my-rewards": "My rewards", + "my-stake": "My stake", + "name-asc": "By name (a-z)", + "name-desc": "By name (z-a)", + "nothing-found": "Nothing found", + "power-asc": "By power (a-z)", + "power-desc": "By power (z-a)", + "random": "Random", + "redelegate-amount": "Redelegation amount", + "redelegate-progress": "Redelegate in progress", + "redelegate-success": "Redelegation successful", + "regular-staking": "Regular staking", + "reward": "Reward", + "rewards-claim-in-progress": "Rewards claim in progress", + "rewards-claimed": "Rewards claimed", + "rewards-progress": "Rewards claim in progress", + "select-new-validator": "Select new validator", + "show-inactive": "Show inactive", + "sort-by": "Sorting by", + "stISLM-in-ISLM": "stISLM in ISLM", + "stride-address-required": "Stride address is required to delegate", + "undelegate-process": "Undelegate in process", + "undelegation-declined": "Undelegation declined", + "undelegation-progress": "Undelegation in progress", + "undelegation-success": "Undelegation successful", + "use-stride-address-placeholder": "Use your Stride address here", + "validator": "Validator", + "validator-inactive-warning": "While the validator is inactive, you will not be able to receive a reward.", + "validator-info-loading": "Fetching validator information", + "validators": "Validators", + "website": "Website", + "you-will-get": "What you'll get:" +} diff --git a/apps/shell/messages/staking/ru.json b/apps/shell/messages/staking/ru.json new file mode 100644 index 000000000..8fd733be3 --- /dev/null +++ b/apps/shell/messages/staking/ru.json @@ -0,0 +1,11 @@ +{ + "commission": "Комиссия", + "delegation-success": "Делегация прошла успешно", + "my-rewards": "Мои награды", + "stISLM-in-ISLM": "stISLM в ISLM", + "validator": "Валидатор", + "validator-info-loading": "Получение информации о валидаторе", + "validators": "Валидаторы", + "website": "Веб-сайт", + "you-will-get": "Что вы получите:" +} diff --git a/apps/shell/messages/tr.json b/apps/shell/messages/tr.json deleted file mode 100644 index a6f1ec71d..000000000 --- a/apps/shell/messages/tr.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "hello-world": "Merhaba Dünya!" -} diff --git a/apps/shell/messages/uc-dao/en.json b/apps/shell/messages/uc-dao/en.json new file mode 100644 index 000000000..653346425 --- /dev/null +++ b/apps/shell/messages/uc-dao/en.json @@ -0,0 +1,20 @@ +{ + "address-placeholder": "Address in EVM/Bech32 format", + "amount": "Amount", + "cancel-button": "Cancel", + "confirm-button": "Confirm", + "confirm-ownership-transfer": "You confirm you want to transfer coins ownership in DAO", + "currency": "Currency", + "dao-balance-label": "DAO Balance", + "dao-page-title": "UnitedContributors DAO", + "deposit-dao": "Deposit to DAO", + "islm-and-liquid-balance": "{balanceNum} ISLM and {tokensNum} LIQUID", + "my-account": "My account", + "send-button": "Send", + "to-the-address": " - to the address {address} ?", + "token-count": "{count, plural, other {{count} LIQUID tokens}}", + "transfer-heading": "Transfer coins ownership", + "transfer-progress": "Transfer in progress", + "transfer-success": "Transfer successful", + "wallet-balance-label": "Wallet Balance" +} diff --git a/apps/shell/messages/utils/en.json b/apps/shell/messages/utils/en.json new file mode 100644 index 000000000..f034fd9bc --- /dev/null +++ b/apps/shell/messages/utils/en.json @@ -0,0 +1,5 @@ +{ + "address-conversion-title" : "Address Conversion", + "converted-address-label" : "Converted address", + "invalid-address" : "Please enter a valid address" +} \ No newline at end of file diff --git a/apps/shell/src/app/[locale]/error.tsx b/apps/shell/src/app/[locale]/error.tsx index 4d63815bb..16279202c 100644 --- a/apps/shell/src/app/[locale]/error.tsx +++ b/apps/shell/src/app/[locale]/error.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect } from 'react'; import * as Sentry from '@sentry/nextjs'; +import { useTranslate } from '@tolgee/react'; import { Button } from '@haqq/shell-ui-kit'; import { Container, Heading } from '@haqq/shell-ui-kit/server'; @@ -11,6 +12,7 @@ export default function ErrorPage({ error: Error & { digest?: string }; reset: () => void; }) { + const { t } = useTranslate('common'); useEffect(() => { // Log the error to Sentry Sentry.captureException(error); @@ -19,7 +21,9 @@ export default function ErrorPage({ return (
- Something went wrong! + + {t('something-went-wrong', 'Something went wrong!')} +
diff --git a/apps/shell/src/app/global.css b/apps/shell/src/app/[locale]/global.css similarity index 100% rename from apps/shell/src/app/global.css rename to apps/shell/src/app/[locale]/global.css diff --git a/apps/shell/src/app/[locale]/layout.tsx b/apps/shell/src/app/[locale]/layout.tsx index 179764434..9cf876844 100644 --- a/apps/shell/src/app/[locale]/layout.tsx +++ b/apps/shell/src/app/[locale]/layout.tsx @@ -23,8 +23,8 @@ import { env } from '../../env/client'; import { clashDisplayFont, hkGuiseFont } from '../../lib/fonts'; import { AppProviders } from '../../providers/app-providers'; import { PHProvider } from '../../providers/posthog-provider'; -import { ALL_LOCALES, getStaticData } from '../../tolgee/shared'; -import '../global.css'; +import { AVAILABLE_LOCALES, getStaticData, Locale } from '../../tolgee/shared'; +import './global.css'; export const metadata: Metadata = { title: { @@ -106,13 +106,13 @@ export default async function RootLayout({ }); } - if (!ALL_LOCALES.includes(params.locale)) { + if (!AVAILABLE_LOCALES.includes(params.locale as Locale)) { notFound(); } // make sure you provide all the necessary locales // for the inital SSR render (e.g. fallback languages) - const locales = await getStaticData([params.locale]); + const locales = await getStaticData([params.locale as Locale]); const dehydratedState = dehydrate(queryClient); diff --git a/apps/shell/src/app/[locale]/not-found.tsx b/apps/shell/src/app/[locale]/not-found.tsx index f4d39c24a..9e4beffe1 100644 --- a/apps/shell/src/app/[locale]/not-found.tsx +++ b/apps/shell/src/app/[locale]/not-found.tsx @@ -1,12 +1,14 @@ 'use client'; +import { useTranslate } from '@tolgee/react'; import { Button } from '@haqq/shell-ui-kit'; import { Heading } from '@haqq/shell-ui-kit/server'; export default function NotFound() { + const { t } = useTranslate('common'); return (
404 - Page not found + {t('page-not-found', 'Page not found')}
diff --git a/apps/shell/src/app/[locale]/staking/validator/[address]/@modals/redelegate/page.tsx b/apps/shell/src/app/[locale]/staking/validator/[address]/@modals/redelegate/page.tsx index 5b545f9f0..a6bfdf0c5 100644 --- a/apps/shell/src/app/[locale]/staking/validator/[address]/@modals/redelegate/page.tsx +++ b/apps/shell/src/app/[locale]/staking/validator/[address]/@modals/redelegate/page.tsx @@ -1,7 +1,6 @@ 'use client'; import { useEffect, useMemo, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; -import { formatUnits } from 'viem'; import { useAddress, useIndexerBalanceQuery, diff --git a/apps/shell/src/app/global-error.tsx b/apps/shell/src/app/global-error.tsx index 4e06b06a2..217d81140 100644 --- a/apps/shell/src/app/global-error.tsx +++ b/apps/shell/src/app/global-error.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect } from 'react'; import * as Sentry from '@sentry/nextjs'; +import { useTranslate } from '@tolgee/react'; import NextError from 'next/error'; import { Button } from '@haqq/shell-ui-kit'; import { Container, Heading } from '@haqq/shell-ui-kit/server'; @@ -12,6 +13,7 @@ export default function GlobalError({ error: Error & { digest?: string }; reset: () => void; }) { + const { t } = useTranslate('common'); useEffect(() => { Sentry.captureException(error); }, [error]); @@ -21,14 +23,16 @@ export default function GlobalError({
- Something went wrong! + + {t('something-went-wrong', 'Something went wrong!')} +
{/* This is the default Next.js error component but it doesn't allow omitting the statusCode property yet. */} diff --git a/apps/shell/src/components/header-desktop.tsx b/apps/shell/src/components/header-desktop.tsx index e809b3750..2d8e312c1 100644 --- a/apps/shell/src/components/header-desktop.tsx +++ b/apps/shell/src/components/header-desktop.tsx @@ -1,10 +1,10 @@ 'use client'; -import { useMemo } from 'react'; import dynamic from 'next/dynamic'; import { useAccount, useChains } from 'wagmi'; import { haqqTestedge2 } from 'wagmi/chains'; import { Header } from '@haqq/shell-ui-kit'; -import { headerLinks } from '../config/header-links'; +import { useFilteredLinks } from '../hooks/use-filtered-header-links'; +import { useLocaleSwitcher } from '../hooks/use-locale-switcher'; const Web3ConnectButtons = dynamic(async () => { const { Web3ConnectButtons } = await import( @@ -13,29 +13,22 @@ const Web3ConnectButtons = dynamic(async () => { return { default: Web3ConnectButtons }; }); -const HeaderUtilsMenu = dynamic(async () => { - const { HeaderUtilsMenu } = await import('../components/header-utils-menu'); - return { default: HeaderUtilsMenu }; -}); - export function AppHeaderDesktop({ className }: { className?: string }) { const chains = useChains(); const { chain = chains[0] } = useAccount(); const isTestedge = chain.id === haqqTestedge2.id; - - const links = useMemo(() => { - return headerLinks.filter((link) => { - return link.chains.includes(chain.id); - }); - }, [chain.id]); + const links = useFilteredLinks(chain); + const { switchLocale, locales, currentLocale } = useLocaleSwitcher(); return (
} - utilsSlot={} className={className} isTestedge={isTestedge} + locales={locales} + switchLocale={switchLocale} + currentLocale={currentLocale} /> ); } diff --git a/apps/shell/src/components/header-mobile.tsx b/apps/shell/src/components/header-mobile.tsx index 8792b15ee..9592e88cb 100644 --- a/apps/shell/src/components/header-mobile.tsx +++ b/apps/shell/src/components/header-mobile.tsx @@ -1,10 +1,9 @@ 'use client'; -import { useMemo } from 'react'; import dynamic from 'next/dynamic'; import { useAccount, useChains } from 'wagmi'; import { haqqTestedge2 } from 'wagmi/chains'; import { HeaderMobile } from '@haqq/shell-ui-kit'; -import { headerLinks } from '../config/header-links'; +import { useFilteredLinks } from '../hooks/use-filtered-header-links'; const Web3ConnectButtonsMobile = dynamic(async () => { const { Web3ConnectButtonsMobile } = await import( @@ -13,27 +12,16 @@ const Web3ConnectButtonsMobile = dynamic(async () => { return { default: Web3ConnectButtonsMobile }; }); -const HeaderUtilsMenu = dynamic(async () => { - const { HeaderUtilsMenu } = await import('../components/header-utils-menu'); - return { default: HeaderUtilsMenu }; -}); - export function AppHeaderMobile({ className }: { className?: string }) { const chains = useChains(); const { chain = chains[0] } = useAccount(); const isTestedge = chain.id === haqqTestedge2.id; - - const links = useMemo(() => { - return headerLinks.filter((link) => { - return link.chains.includes(chain.id); - }); - }, [chain.id]); + const links = useFilteredLinks(chain); return ( } - utilsSlot={} className={className} isTestedge={isTestedge} /> diff --git a/apps/shell/src/components/header-utils-menu.tsx b/apps/shell/src/components/header-utils-menu.tsx deleted file mode 100644 index 0674469e9..000000000 --- a/apps/shell/src/components/header-utils-menu.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'; -import clsx from 'clsx'; -import Link from 'next/link'; -import { ArrowDownIcon } from '@haqq/shell-ui-kit/server'; -import { headerUtilsLinks } from '../config/header-links'; - -export function HeaderUtilsMenu() { - return ( - - -
Utils
- -
- - - {headerUtilsLinks.map(({ name, link }) => { - return ( - - {name} - - ); - })} - -
- ); -} diff --git a/apps/shell/src/components/locale-dropdown.tsx b/apps/shell/src/components/locale-dropdown.tsx new file mode 100644 index 000000000..5f4d64bf0 --- /dev/null +++ b/apps/shell/src/components/locale-dropdown.tsx @@ -0,0 +1,14 @@ +import { LocaleDropdown } from '@haqq/shell-ui-kit'; +import { useLocaleSwitcher } from '../hooks/use-locale-switcher'; + +export function AppLocaleDropdown() { + const { switchLocale, locales, currentLocale } = useLocaleSwitcher(); + + return ( + + ); +} diff --git a/apps/shell/src/components/web3-connect-button.tsx b/apps/shell/src/components/web3-connect-button.tsx index 2844b546f..e2d6b0c6d 100644 --- a/apps/shell/src/components/web3-connect-button.tsx +++ b/apps/shell/src/components/web3-connect-button.tsx @@ -1,4 +1,5 @@ 'use client'; +import { useTranslate } from '@tolgee/react'; import { useAccount, useChains } from 'wagmi'; import { getFormattedAddress, @@ -10,6 +11,7 @@ import { Button, AccountButton, SelectChainButton } from '@haqq/shell-ui-kit'; import { formatNumber } from '@haqq/shell-ui-kit/server'; export function Web3ConnectButtons() { + const { t } = useTranslate('common'); const { isConnected, chain } = useAccount(); const { haqqAddress, ethAddress } = useAddress(); const chains = useChains(); @@ -19,7 +21,9 @@ export function Web3ConnectButtons() { if (!isConnected || !ethAddress) { return (
- +
); } @@ -63,6 +67,7 @@ export function Web3ConnectButtons() { } export function Web3ConnectButtonsMobile() { + const { t } = useTranslate('common'); const { isConnected, chain } = useAccount(); const { haqqAddress, ethAddress } = useAddress(); const chains = useChains(); @@ -72,7 +77,9 @@ export function Web3ConnectButtonsMobile() { if (!isConnected || !ethAddress) { return (
- +
); } @@ -113,7 +120,7 @@ export function Web3ConnectButtonsMobile() { />
- +
); diff --git a/apps/shell/src/config/header-links.ts b/apps/shell/src/config/header-links.ts deleted file mode 100644 index e87f0d128..000000000 --- a/apps/shell/src/config/header-links.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { haqqMainnet, haqqTestedge2 } from 'wagmi/chains'; - -export const headerLinks: { - href: string; - label: string; - chains: number[]; -}[] = [ - // { - // href: '/', - // label: 'Home', - // chains: [haqqMainnet.id, haqqTestedge2.id], - // }, - { - href: '/uc-dao', - label: 'UC DAO', - chains: [haqqMainnet.id, haqqTestedge2.id], - }, - { - href: '/staking', - label: 'Staking', - chains: [haqqMainnet.id, haqqTestedge2.id], - }, - { - href: '/governance', - label: 'Governance', - chains: [haqqMainnet.id, haqqTestedge2.id], - }, - { - href: '/authz', - label: 'Authz', - chains: [haqqMainnet.id, haqqTestedge2.id], - }, - { - href: '/faucet', - label: 'Faucet', - chains: [haqqTestedge2.id], - }, -]; - -export const headerUtilsLinks: { - name: string; - link: string; -}[] = [{ name: 'Address conversion', link: '/utils/address-conversion' }]; diff --git a/apps/shell/src/config/use-header-links.tsx b/apps/shell/src/config/use-header-links.tsx new file mode 100644 index 000000000..48e2c6240 --- /dev/null +++ b/apps/shell/src/config/use-header-links.tsx @@ -0,0 +1,59 @@ +import { useTranslate } from '@tolgee/react'; +import { haqqMainnet, haqqTestedge2 } from 'wagmi/chains'; +import { HeaderLink } from '@haqq/shell-ui-kit'; + +export const useHeaderLinks = (): HeaderLink[] => { + const { t } = useTranslate('common'); + return [ + { + type: 'link', + label: t('uc-dao', 'UC DAO'), + href: '/uc-dao', + chains: [haqqMainnet.id, haqqTestedge2.id], + }, + { + type: 'link', + label: t('staking', 'Staking'), + href: '/staking', + chains: [haqqMainnet.id, haqqTestedge2.id], + }, + { + type: 'link', + label: t('governance', 'Governance'), + href: '/governance', + chains: [haqqMainnet.id, haqqTestedge2.id], + }, + { + type: 'dropdown', + label: t('tools', 'Tools'), + children: [ + { + type: 'link', + label: t('authz', 'Authz'), + href: '/authz', + chains: [haqqMainnet.id, haqqTestedge2.id], + }, + { + type: 'link', + label: t('faucet', 'Faucet'), + href: '/faucet', + chains: [haqqTestedge2.id], + }, + ], + }, + { + type: 'dropdown', + label: t('utils', 'Utils'), + children: [ + { + type: 'link', + label: t('address-conversion-title', 'Address conversion', { + ns: 'common', + }), + href: '/utils/address-conversion', + chains: [haqqMainnet.id, haqqTestedge2.id], + }, + ], + }, + ]; +}; diff --git a/apps/shell/src/env/client.ts b/apps/shell/src/env/client.ts index e01b28645..1922521a1 100644 --- a/apps/shell/src/env/client.ts +++ b/apps/shell/src/env/client.ts @@ -11,6 +11,8 @@ export const env = createEnv({ NEXT_PUBLIC_POSTHOG_HOST: z.string().min(3).optional(), NEXT_PUBLIC_POSTHOG_KEY: z.string().min(3).optional(), NEXT_PUBLIC_GIT_COMMIT_SHA: z.string().min(3).optional(), + NEXT_PUBLIC_TOLGEE_API_KEY: z.string().min(1), + NEXT_PUBLIC_TOLGEE_API_URL: z.string().min(1), }, experimental__runtimeEnv: { NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: @@ -30,5 +32,7 @@ export const env = createEnv({ process.env['NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA'] ?? process.env['VERCEL_GIT_COMMIT_SHA'] ?? 'dev', + NEXT_PUBLIC_TOLGEE_API_KEY: process.env['NEXT_PUBLIC_TOLGEE_API_KEY'], + NEXT_PUBLIC_TOLGEE_API_URL: process.env['NEXT_PUBLIC_TOLGEE_API_URL'], }, }); diff --git a/apps/shell/src/hooks/use-available-locale-links.tsx b/apps/shell/src/hooks/use-available-locale-links.tsx new file mode 100644 index 000000000..46d40ac60 --- /dev/null +++ b/apps/shell/src/hooks/use-available-locale-links.tsx @@ -0,0 +1,10 @@ +import { AVAILABLE_LOCALES, LOCALE_LABELS } from '../tolgee/shared'; + +export function useAvailableLocaleLinks() { + return AVAILABLE_LOCALES.map((locale) => { + return { + id: locale, + label: `${LOCALE_LABELS[locale].emoji} ${LOCALE_LABELS[locale].label}`, + }; + }); +} diff --git a/apps/shell/src/hooks/use-filtered-header-links.ts b/apps/shell/src/hooks/use-filtered-header-links.ts new file mode 100644 index 000000000..f380d9a1c --- /dev/null +++ b/apps/shell/src/hooks/use-filtered-header-links.ts @@ -0,0 +1,31 @@ +import { useMemo } from 'react'; +import { Chain } from 'wagmi/chains'; +import { HeaderLink } from '@haqq/shell-ui-kit'; +import { useHeaderLinks } from '../config/use-header-links'; + +export function useFilteredLinks(chain: Chain) { + const headerLinks = useHeaderLinks(); + const links = useMemo(() => { + return headerLinks + .map((link) => { + if (link.type === 'dropdown') { + const filteredChildren = link.children.filter((child) => { + return !child.chains || child.chains.includes(chain.id); + }); + if (filteredChildren.length > 0) { + return { ...link, children: filteredChildren }; + } + return null; + } + + if (link.type === 'link' && link.chains.includes(chain.id)) { + return link; + } + + return null; + }) + .filter(Boolean) as HeaderLink[]; + }, [chain.id, headerLinks]); + + return links; +} diff --git a/apps/shell/src/hooks/use-locale-switcher.tsx b/apps/shell/src/hooks/use-locale-switcher.tsx new file mode 100644 index 000000000..87bc7930f --- /dev/null +++ b/apps/shell/src/hooks/use-locale-switcher.tsx @@ -0,0 +1,32 @@ +'use client'; +import { useCallback } from 'react'; +import { useLocale } from 'next-intl'; +import { useRouter, usePathname } from '../i18n/routing'; +import { AVAILABLE_LOCALES, LOCALE_LABELS } from '../tolgee/shared'; + +export function useLocaleSwitcher() { + const router = useRouter(); + const pathname = usePathname(); + const currentLocale = useLocale(); + + const switchLocale = useCallback( + (locale: string) => { + router.replace(pathname, { locale }); + }, + [router, pathname], + ); + + const locales = AVAILABLE_LOCALES.map((locale) => { + return { + id: locale, + label: LOCALE_LABELS[locale].label, + emoji: LOCALE_LABELS[locale].emoji, + }; + }); + + return { + switchLocale, + locales, + currentLocale, + }; +} diff --git a/apps/shell/src/i18n/request.ts b/apps/shell/src/i18n/request.ts index 271db2057..182fc7b80 100644 --- a/apps/shell/src/i18n/request.ts +++ b/apps/shell/src/i18n/request.ts @@ -1,10 +1,17 @@ import { getRequestConfig } from 'next-intl/server'; +import { routing } from './routing'; +import { Locale } from '../tolgee/shared'; + +export default getRequestConfig(async ({ requestLocale }) => { + // Get locale from request and fallback to default if needed + let locale = await requestLocale; + + if (!locale || !routing.locales.includes(locale as Locale)) { + locale = routing.defaultLocale; + } -// The i18n/request.ts is required by next-intl package, we don't actually need it, -// so we are only doing necessary actions to stop next-intl from complaining. -export default getRequestConfig(async ({ locale }) => { return { - // do this to make next-intl not emmit any warnings + locale, messages: { locale }, }; }); diff --git a/apps/shell/src/i18n/routing.ts b/apps/shell/src/i18n/routing.ts new file mode 100644 index 000000000..41a72778b --- /dev/null +++ b/apps/shell/src/i18n/routing.ts @@ -0,0 +1,17 @@ +import { createNavigation } from 'next-intl/navigation'; +import { defineRouting } from 'next-intl/routing'; +import { AVAILABLE_LOCALES, DEFAULT_LOCALE } from '../tolgee/shared'; + +export const routing = defineRouting({ + locales: AVAILABLE_LOCALES, + defaultLocale: DEFAULT_LOCALE, + localeCookie: { + // Custom cookie name + name: 'USER_LOCALE', + // Expire in one day + maxAge: 60 * 60 * 24, + }, +}); + +export const { Link, redirect, usePathname, useRouter, permanentRedirect } = + createNavigation(routing); diff --git a/apps/shell/src/middleware.ts b/apps/shell/src/middleware.ts index 55c57f2c1..5463e5594 100644 --- a/apps/shell/src/middleware.ts +++ b/apps/shell/src/middleware.ts @@ -1,10 +1,10 @@ import createMiddleware from 'next-intl/middleware'; -import { ALL_LOCALES, DEFAULT_LOCALE } from './tolgee/shared'; +import { AVAILABLE_LOCALES, DEFAULT_LOCALE } from './tolgee/shared'; export default createMiddleware({ - locales: ALL_LOCALES, + locales: AVAILABLE_LOCALES, defaultLocale: DEFAULT_LOCALE, - localePrefix: 'as-needed', + localePrefix: 'never', }); export const config = { diff --git a/apps/shell/src/navigation.ts b/apps/shell/src/navigation.ts deleted file mode 100644 index 7ac72acef..000000000 --- a/apps/shell/src/navigation.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createSharedPathnamesNavigation } from 'next-intl/navigation'; -import { ALL_LOCALES } from './tolgee/shared'; - -export const { Link, redirect, usePathname, useRouter } = - createSharedPathnamesNavigation({ locales: ALL_LOCALES }); diff --git a/apps/shell/src/providers/app-providers.tsx b/apps/shell/src/providers/app-providers.tsx index a6af494c1..869368797 100644 --- a/apps/shell/src/providers/app-providers.tsx +++ b/apps/shell/src/providers/app-providers.tsx @@ -1,6 +1,7 @@ 'use client'; import { PropsWithChildren } from 'react'; import { DehydratedState } from '@tanstack/react-query'; +import { TolgeeStaticData } from '@tolgee/web'; import { State, WagmiProvider, Config } from 'wagmi'; import { CosmosProvider, @@ -28,7 +29,7 @@ export function AppProviders({ dehydratedState?: DehydratedState; wagmiConfig?: Config; isMobileUA: boolean; - locales: Record; + locales: TolgeeStaticData; locale: string; }>) { const actualWagmiConfig = wagmiConfig diff --git a/apps/shell/src/tolgee/client.tsx b/apps/shell/src/tolgee/client.tsx index 64734af0d..c44be8679 100644 --- a/apps/shell/src/tolgee/client.tsx +++ b/apps/shell/src/tolgee/client.tsx @@ -1,17 +1,32 @@ 'use client'; -import { useEffect } from 'react'; -import { TolgeeProvider, useTolgeeSSR } from '@tolgee/react'; +import { ReactNode, useEffect } from 'react'; +import { TolgeeProvider, TolgeeStaticData, useTolgeeSSR } from '@tolgee/react'; import { useRouter } from 'next/navigation'; -import { TolgeeBase } from './shared'; +import { AVAILABLE_LOCALES, ALL_NAMESPACES, TolgeeBase } from './shared'; type Props = { - locales: any; + locales: TolgeeStaticData; locale: string; - children: React.ReactNode; + children: ReactNode; }; -const tolgee = TolgeeBase().init(); +const staticData: TolgeeStaticData = {}; + +AVAILABLE_LOCALES.forEach((loc) => { + ALL_NAMESPACES.forEach((ns) => { + staticData[`${loc}:${ns}`] = async () => { + const data = await import(`../i18n/${loc}.json`); + return data[ns]; + }; + }); +}); + +const tolgee = TolgeeBase().init({ + defaultNs: 'common', + defaultLanguage: 'en', + staticData, +}); export const TolgeeNextProvider = ({ locale, locales, children }: Props) => { // synchronize SSR and client first render diff --git a/apps/shell/src/tolgee/server.tsx b/apps/shell/src/tolgee/server.tsx index 7a7b7cc49..696622511 100644 --- a/apps/shell/src/tolgee/server.tsx +++ b/apps/shell/src/tolgee/server.tsx @@ -1,13 +1,13 @@ import { createServerInstance } from '@tolgee/react/server'; import { getLocale } from 'next-intl/server'; -import { TolgeeBase, ALL_LOCALES, getStaticData } from './shared'; +import { TolgeeBase, AVAILABLE_LOCALES, getStaticData } from './shared'; export const { getTolgee, getTranslate, T } = createServerInstance({ getLocale, createTolgee: async (locale) => { return TolgeeBase().init({ // load all languages on the server - staticData: await getStaticData(ALL_LOCALES), + staticData: await getStaticData(AVAILABLE_LOCALES as unknown as string[]), observerOptions: { fullKeyEncode: true, }, diff --git a/apps/shell/src/tolgee/shared.ts b/apps/shell/src/tolgee/shared.ts index a6da327f6..005fb406e 100644 --- a/apps/shell/src/tolgee/shared.ts +++ b/apps/shell/src/tolgee/shared.ts @@ -1,17 +1,56 @@ -import { DevTools, Tolgee, FormatSimple } from '@tolgee/web'; +import { FormatIcu } from '@tolgee/format-icu'; +import { DevTools, Tolgee, FormatSimple, TolgeeStaticData } from '@tolgee/web'; +import { env } from '../env/client'; -export const ALL_LOCALES = ['en', 'ar', 'id', 'tr']; +export const AVAILABLE_LOCALES = ['en', 'ar', 'id', 'tr', 'ru'] as const; +export type Locale = (typeof AVAILABLE_LOCALES)[number]; + +export const LOCALE_LABELS: Record = { + en: { label: 'English', emoji: '🇬🇧' }, + ar: { label: 'العربية', emoji: '🇸🇦' }, + id: { label: 'Bahasa Indonesia', emoji: '🇮🇩' }, + tr: { label: 'Türkçe', emoji: '🇹🇷' }, + ru: { label: 'Русский', emoji: '🇷🇺' }, +}; + +export type AllNamespaces = (typeof ALL_NAMESPACES)[number]; + +export const ALL_NAMESPACES = [ + 'common', + 'utils', + 'uc-dao', + 'main', + 'staking', + 'governance', + 'authz', + 'faucet', +] as const; export const DEFAULT_LOCALE = 'en'; -const apiKey = process.env.NEXT_PUBLIC_TOLGEE_API_KEY; -const apiUrl = process.env.NEXT_PUBLIC_TOLGEE_API_URL; +const apiKey = env.NEXT_PUBLIC_TOLGEE_API_KEY; +const apiUrl = env.NEXT_PUBLIC_TOLGEE_API_URL; + +export async function getStaticData( + languages: string[], +): Promise { + const result: TolgeeStaticData = {}; -export async function getStaticData(languages: string[]) { - const result: Record = {}; for (const lang of languages) { - result[lang] = (await import(`../../messages/${lang}.json`)).default; + for (const ns of ALL_NAMESPACES) { + try { + const data = (await import(`../../messages/${lang}/${ns}.json`)) + .default; + result[`${lang}:${ns}`] = data; + } catch (error) { + console.error( + `Error loading namespace "${ns}" for language "${lang}":`, + { error, languages, ALL_NAMESPACES }, + ); + } + } } + return result; } @@ -20,10 +59,8 @@ export function TolgeeBase() { Tolgee() .use(FormatSimple()) .use(DevTools()) + .use(FormatIcu()) // Preset shared settings - .updateDefaults({ - apiKey, - apiUrl, - }) + .updateDefaults({ apiKey, apiUrl }) ); } diff --git a/libs/address-conversion/src/lib/address-conversion-page.tsx b/libs/address-conversion/src/lib/address-conversion-page.tsx index d52613902..42d7ed702 100644 --- a/libs/address-conversion/src/lib/address-conversion-page.tsx +++ b/libs/address-conversion/src/lib/address-conversion-page.tsx @@ -1,6 +1,7 @@ 'use client'; import { ChangeEvent, useCallback, useEffect, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import debounce from 'lodash/debounce'; import { @@ -25,6 +26,7 @@ import { LabeledBlock } from './components/labeled-block'; const symbol = 'ISLM'; export function AddressConversionPage() { + const { t } = useTranslate(); const { copyText } = useClipboard(); const [enteredAddress, setEnteredAddress] = useState(''); @@ -54,14 +56,16 @@ export function AddressConversionPage() { } else { setConvertedAddress(''); setValidHaqqAddress(''); - setInputError('Please enter a valid address'); + setInputError( + t('invalid-address', 'Please enter a valid address', { ns: 'utils' }), + ); } } else { setInputError(''); setValidHaqqAddress(''); setConvertedAddress(''); } - }, [enteredAddress]); + }, [enteredAddress, t]); const handleValidateAddressDebounced = debounce(handleValidateAddress, 200); @@ -89,7 +93,9 @@ export function AddressConversionPage() {
- Address Conversion + {t('address-conversion-title', 'Address Conversion', { + ns: 'common', + })}
@@ -98,6 +104,7 @@ export function AddressConversionPage() {
+ {/* eslint-disable-next-line i18next/no-literal-string */} Bech32 / EVM @@ -106,7 +113,7 @@ export function AddressConversionPage() {
Grant in progress, + loading: ( + + {t('grant-loading', 'Grant in progress', { ns: 'authz' })} + + ), success: (tx) => { console.log('Grant successful', { tx }); const txHash = tx?.txhash; @@ -202,7 +209,9 @@ export function AuthzGrantsActions() { return (
-
Grant successful
+
+ {t('grant-success', 'Grant successful', { ns: 'authz' })} +
- Grant access + {t('grant-access', 'Grant access', { ns: 'authz' })}
@@ -317,7 +327,9 @@ export function AuthzGrantsActions() { htmlFor="grantee" className="cursor-pointer text-[12px] font-[500] uppercase leading-[24px] text-white/50" > - Grantee address + {t('grantee-address', 'Grantee address', { + ns: 'authz', + })}
@@ -344,7 +356,7 @@ export function AuthzGrantsActions() {
{ if (period) { @@ -376,7 +390,7 @@ export function AuthzGrantsActions() { htmlFor="memo" className="cursor-pointer text-[12px] font-[500] uppercase leading-[24px] text-white/50" > - Memo + {t('memo', 'Memo', { ns: 'authz' })}
@@ -389,7 +403,9 @@ export function AuthzGrantsActions() { 'max-w-xl', )} type="text" - placeholder="Add your memo" + placeholder={t('add-your-memo', 'Add your memo', { + ns: 'common', + })} id="memo" name="memo" value={memo} @@ -408,7 +424,7 @@ export function AuthzGrantsActions() { variant={2} disabled={!isGranteeValid} > - Grant Access + {t('grant-access', 'Grant access', { ns: 'authz' })}
@@ -428,14 +444,22 @@ export function AuthzGrantsActions() {
- Selected grantee + {t('selected-grantee', 'Selected grantee', { + ns: 'authz', + })}
- You should enter valid grantee wallet to see info + {t( + 'invalid-grantee-wallet-message', + 'You should enter valid grantee wallet to see info', + { + ns: 'authz', + }, + )}
diff --git a/libs/authz/src/lib/authz-page.tsx b/libs/authz/src/lib/authz-page.tsx index c140c7ccb..589d91745 100644 --- a/libs/authz/src/lib/authz-page.tsx +++ b/libs/authz/src/lib/authz-page.tsx @@ -1,4 +1,5 @@ 'use client'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { useAddress, useWallet } from '@haqq/shell-shared'; import { Button } from '@haqq/shell-ui-kit'; @@ -10,6 +11,7 @@ import { GranterGrantsTable } from './granter-table'; export function AuthzPage() { const { ethAddress } = useAddress(); const { openSelectWallet, isHaqqWallet } = useWallet(); + const { t } = useTranslate('common'); return (
@@ -17,7 +19,7 @@ export function AuthzPage() {
- Authz + {t('authz', 'Authz')}
@@ -32,10 +34,10 @@ export function AuthzPage() { )} >
- You should connect wallet first + {t('connect-wallet-message', 'You should connect wallet first')}
) : ( diff --git a/libs/authz/src/lib/grantee-card.tsx b/libs/authz/src/lib/grantee-card.tsx index a38f4ca5a..275e52155 100644 --- a/libs/authz/src/lib/grantee-card.tsx +++ b/libs/authz/src/lib/grantee-card.tsx @@ -6,6 +6,7 @@ import { useMemo, useState, } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { formatUnits, parseUnits } from 'viem'; import { @@ -72,6 +73,7 @@ export function GranteeCard({ haqq: string; }; }) { + const { t } = useTranslate(); const [isEthAddressCopy, setEthAddressCopy] = useState(false); const [isHaqqAddressCopy, setHaqqAddressCopy] = useState(false); const { copyText } = useClipboard(); @@ -152,22 +154,25 @@ export function GranteeCard({
- Selected grantee + {t('selected-grantee', 'Selected grantee', { ns: 'authz' })}
@@ -226,7 +232,7 @@ export function GranteeCard({
diff --git a/libs/authz/src/lib/grantee-table.tsx b/libs/authz/src/lib/grantee-table.tsx index c1ceec8c8..8fd61dbc5 100644 --- a/libs/authz/src/lib/grantee-table.tsx +++ b/libs/authz/src/lib/grantee-table.tsx @@ -1,9 +1,11 @@ import { useMemo } from 'react'; +import { useTranslate } from '@tolgee/react'; import { useAddress, useAuthzGranteeGrants } from '@haqq/shell-shared'; import { Container, Heading } from '@haqq/shell-ui-kit/server'; import { mapRPCGrantToWebGrant } from './utils/map-rpc-grant-to-web-grant'; export function GranteeGrantsTable() { + const { t } = useTranslate('authz'); const { haqqAddress } = useAddress(); const { data: granteeGrants } = useAuthzGranteeGrants(haqqAddress ?? ''); @@ -25,7 +27,7 @@ export function GranteeGrantsTable() {
- Access you have been granted + {t('access-granted-to-you', 'Access you have been granted')}
@@ -33,13 +35,13 @@ export function GranteeGrantsTable() { - Granter + {t('granter', 'Granter')} - Message + {t('message', 'Message')} {/* - Valid tru + {t('valid-thru', 'Valid thru')} */} diff --git a/libs/authz/src/lib/granter-table.tsx b/libs/authz/src/lib/granter-table.tsx index 049c3272e..078fdf4c2 100644 --- a/libs/authz/src/lib/granter-table.tsx +++ b/libs/authz/src/lib/granter-table.tsx @@ -1,4 +1,5 @@ import { useMemo } from 'react'; +import { useTranslate } from '@tolgee/react'; import dynamic from 'next/dynamic'; import { useAddress, useAuthzGranterGrants } from '@haqq/shell-shared'; import { Container, Heading, formatDate } from '@haqq/shell-ui-kit/server'; @@ -17,6 +18,7 @@ const RevokeButton = dynamic( ); export function GranterGrantsTable() { + const { t } = useTranslate('authz'); const { haqqAddress } = useAddress(); const { data: granterGrants } = useAuthzGranterGrants(haqqAddress ?? ''); @@ -38,7 +40,7 @@ export function GranterGrantsTable() {
- Access you have granted + {t('access-you-granted', 'Access you have granted')}
@@ -46,13 +48,13 @@ export function GranterGrantsTable() { - Grantee + {t('grantee', 'Grantee')} - Message + {t('message', 'Message')} - Valid thru + {t('valid-thru', 'Valid thru')}   diff --git a/libs/authz/src/lib/revoke-button.tsx b/libs/authz/src/lib/revoke-button.tsx index 59a10f553..1ba2b8d06 100644 --- a/libs/authz/src/lib/revoke-button.tsx +++ b/libs/authz/src/lib/revoke-button.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback } from 'react'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { useAccount, useChains } from 'wagmi'; import { haqqMainnet } from 'wagmi/chains'; @@ -27,6 +28,7 @@ export function RevokeButton({ grantee: string; msg: string; }) { + const { t } = useTranslate('authz'); const invalidateQueries = useQueryInvalidate(); const { revoke, getRevokeEstimatedFee } = useAuthzActions(); const toast = useToast(); @@ -48,7 +50,11 @@ export function RevokeButton({ ); await toast.promise(revokePromise, { - loading: Revoke in progress, + loading: ( + + {t('revoke-loading', 'Revoke in progress')} + + ), success: (tx) => { console.log('Revoke successful', { tx }); const txHash = tx?.txhash; @@ -56,7 +62,7 @@ export function RevokeButton({ return (
-
Revoke successful
+
{t('revoke-success', 'Revoke successful')}
- Revoke + {t('revoke-button', 'Revoke')} ); } diff --git a/libs/faucet/src/lib/components/account-info.tsx b/libs/faucet/src/lib/components/account-info.tsx index 3c244d297..e52b8e442 100644 --- a/libs/faucet/src/lib/components/account-info.tsx +++ b/libs/faucet/src/lib/components/account-info.tsx @@ -1,5 +1,6 @@ 'use client'; import { PropsWithChildren, useCallback, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import { getFormattedAddress, useAddress, @@ -26,6 +27,7 @@ export function MyAccountCardBlock({ } export function AccountInfo() { + const { t } = useTranslate(); const { ethAddress, haqqAddress } = useAddress(); const { copyText } = useClipboard(); const { data: balance } = useIndexerBalanceQuery(haqqAddress); @@ -58,12 +60,17 @@ export function AccountInfo() {
{ethAddress && (
- +
+
{formatNumber(balance.balance)} ISLM
diff --git a/libs/faucet/src/lib/faucet-page.tsx b/libs/faucet/src/lib/faucet-page.tsx index b43cb1fb1..ab36fb580 100644 --- a/libs/faucet/src/lib/faucet-page.tsx +++ b/libs/faucet/src/lib/faucet-page.tsx @@ -8,6 +8,7 @@ import { useState, } from 'react'; import { useAuth0 } from '@auth0/auth0-react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { notFound } from 'next/navigation'; import SuccessIndicator from 'react-success-indicator'; @@ -38,6 +39,7 @@ export function FaucetPage({ serviceEndpoint: string; reCaptchaSiteKey: string; }): ReactElement { + const { t } = useTranslate(); const chains = useChains(); const { chain = chains[0] } = useAccount(); const { switchChainAsync } = useSwitchChain(); @@ -182,7 +184,7 @@ export function FaucetPage({
- Faucet + {t('faucet', 'Faucet', { ns: 'common' })}
@@ -202,7 +204,7 @@ export function FaucetPage({
- Wallet + {t('wallet', 'Wallet', { ns: 'faucet' })}
@@ -211,7 +213,10 @@ export function FaucetPage({ className="font-clash mb-[-2px] text-end !text-[12px] uppercase" onClick={handleNetworkSwitch} > - Switch to {chain.name} + {t('switch-chain', 'Switch to {chain}', { + ns: 'faucet', + chain: chain.name, + })} )}
@@ -256,7 +261,7 @@ export function FaucetPage({ - Github + {t('github', 'Github', { ns: 'faucet' })}
@@ -284,7 +289,7 @@ export function FaucetPage({ disabled={isAuth0Loading} variant={2} > - Login with github + {t('login-github', 'Login with github', { ns: 'faucet' })} )}
@@ -295,7 +300,7 @@ export function FaucetPage({
- Claim tokens + {t('claim-tokens', 'Claim tokens', { ns: 'faucet' })}
@@ -314,7 +319,9 @@ export function FaucetPage({ className="w-full" onClick={handleRequestTokens} > - Request tokens + {t('request-tokens', 'Request tokens', { + ns: 'faucet', + })}
) : ( @@ -333,7 +340,9 @@ export function FaucetPage({

- Tokens claimed! + {t('tokens-claimed', 'Tokens claimed', { + ns: 'faucet', + })}

)} @@ -344,7 +353,11 @@ export function FaucetPage({ date={ new Date(Date.now() + claimInfo.next_claim_sec * 1000) } - title="Next request tokens available after" + title={t( + 'next-token-request-available', + 'Next request tokens available after', + { ns: 'faucet' }, + )} /> )} diff --git a/libs/governance/src/index.ts b/libs/governance/src/index.ts index 5873e7a12..dfc96009f 100644 --- a/libs/governance/src/index.ts +++ b/libs/governance/src/index.ts @@ -1,3 +1,4 @@ export * from './lib/proposal-list-page'; export * from './lib/proposal-details-page'; export * from './lib/components/proposal-list-card'; +export * from './lib/hooks/useGetProposalTypeText'; diff --git a/libs/governance/src/lib/components/parameter-change-proposal.tsx b/libs/governance/src/lib/components/parameter-change-proposal.tsx index cceb252a7..193872130 100644 --- a/libs/governance/src/lib/components/parameter-change-proposal.tsx +++ b/libs/governance/src/lib/components/parameter-change-proposal.tsx @@ -1,4 +1,5 @@ import { ParameterChangeProposalContent } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import { Heading } from '@haqq/shell-ui-kit/server'; import { Metadata } from './metadata'; @@ -7,6 +8,7 @@ export function ParameterChangeProposalDetails({ }: { content: ParameterChangeProposalContent; }) { + const { t } = useTranslate('governance'); return (
@@ -26,7 +28,7 @@ export function ParameterChangeProposalDetails({ - Parameter changes + {t('parameter-changes', 'Parameter changes')}
{JSON.stringify(content.changes, null, 2)} diff --git a/libs/governance/src/lib/components/proposal-deposit-modal.tsx b/libs/governance/src/lib/components/proposal-deposit-modal.tsx index c262a88f2..4f8e7952b 100644 --- a/libs/governance/src/lib/components/proposal-deposit-modal.tsx +++ b/libs/governance/src/lib/components/proposal-deposit-modal.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { Button, @@ -65,6 +66,7 @@ export function ProposalDepositModal({ onSubmit: (amount: number) => void; isPending?: boolean; }) { + const { t } = useTranslate(); const [depositAmount, setDepositAmount] = useState( undefined, ); @@ -101,13 +103,23 @@ export function ProposalDepositModal({ const amountHint = useMemo(() => { if (amountError === 'min') { - return Bellow minimal value; + return ( + + {t('amount-error-min', 'Bellow minimal value', { ns: 'common' })} + + ); } else if (amountError === 'max') { - return More than you have; + return ( + + {t('amount-error-more-than-have', 'More than you have', { + ns: 'common', + })} + + ); } return undefined; - }, [amountError]); + }, [amountError, t]); const handleMaxButtonClick = useCallback(() => { setDepositAmount(balance); @@ -129,12 +141,14 @@ export function ProposalDepositModal({
- Deposit + + {t('deposit', 'Deposit', { ns: 'common' })} +
@@ -157,7 +171,9 @@ export function ProposalDepositModal({ disabled={!isDepositEnabled || !depositAmount} isLoading={isPending} > - Confirm delegation + {t('confirm-delegation', 'Confirm delegation', { + ns: 'common', + })}
diff --git a/libs/governance/src/lib/components/proposal-list-card.tsx b/libs/governance/src/lib/components/proposal-list-card.tsx index f40f369de..a391e1c3e 100644 --- a/libs/governance/src/lib/components/proposal-list-card.tsx +++ b/libs/governance/src/lib/components/proposal-list-card.tsx @@ -6,7 +6,7 @@ import { TallyResults, } from '@haqq/data-access-cosmos'; import { ProposalCard, ProposalStatusEnum } from '@haqq/shell-ui-kit/server'; -import { getProposalTypeText } from '../proposal-details-page'; +import { useGetProposalTypeText } from '../hooks/useGetProposalTypeText'; export function ProposalListCard({ proposal, @@ -54,7 +54,7 @@ export function ProposalListCard({ minDeposit={minDeposit} results={proposalTally} symbol={symbol} - type={getProposalTypeText(proposal.content['@type'])} + type={useGetProposalTypeText(proposal.content['@type'])} userVote={userVote} className={className} /> diff --git a/libs/governance/src/lib/components/software-upgrade-proposal.tsx b/libs/governance/src/lib/components/software-upgrade-proposal.tsx index 2ca1cb0ca..93579308f 100644 --- a/libs/governance/src/lib/components/software-upgrade-proposal.tsx +++ b/libs/governance/src/lib/components/software-upgrade-proposal.tsx @@ -1,4 +1,5 @@ import { useMemo } from 'react'; +import { useTranslate } from '@tolgee/react'; import { Heading } from '@haqq/shell-ui-kit/server'; import { Metadata } from './metadata'; @@ -15,6 +16,7 @@ export function SoftwareUpgradeProposalDetails({ }: { plan: SoftwareUpgradeProposalPlan; }) { + const { t } = useTranslate('governance'); const formattedPlan = useMemo(() => { return { name: plan.name, @@ -44,7 +46,7 @@ export function SoftwareUpgradeProposalDetails({ - Upgrade plan + {t('upgrade-plan', 'Upgrade plan')}
diff --git a/libs/governance/src/lib/hooks/useGetProposalTypeText.tsx b/libs/governance/src/lib/hooks/useGetProposalTypeText.tsx new file mode 100644 index 000000000..2306d0f0a --- /dev/null +++ b/libs/governance/src/lib/hooks/useGetProposalTypeText.tsx @@ -0,0 +1,44 @@ +import { useTranslate } from '@tolgee/react'; + +export const enum ProposalTypes { + Text = '/cosmos.gov.v1beta1.TextProposal', + SoftwareUpgrade = '/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal', + CancelSoftwareUpgrade = '/cosmos.upgrade.v1beta1.CancelSoftwareUpgradeProposal', + ParameterChange = '/cosmos.params.v1beta1.ParameterChangeProposal', + ClientUpdate = '/ibc.core.client.v1.ClientUpdateProposal', + RegisterCoin = '/evmos.erc20.v1.RegisterCoinProposal', + RegisterERC20 = '/evmos.erc20.v1.RegisterERC20Proposal', +} + +export function useGetProposalTypeText(type: string) { + const { t } = useTranslate('governance'); + + switch (type) { + case ProposalTypes.Text: + return t('proposal-types.text', 'Text'); + + case ProposalTypes.SoftwareUpgrade: + return t('proposal-types.software-upgrade', 'Software upgrade'); + + case ProposalTypes.CancelSoftwareUpgrade: + return t( + 'proposal-types.cancel-software-upgrade', + 'Cancel software upgrade', + ); + + case ProposalTypes.ClientUpdate: + return t('proposal-types.client-update', 'Client update'); + + case ProposalTypes.ParameterChange: + return t('proposal-types.parameter-change', 'Parameter change'); + + case ProposalTypes.RegisterCoin: + return t('proposal-types.register-coin', 'Register coin'); + + case ProposalTypes.RegisterERC20: + return t('proposal-types.register-erc20', 'Register ERC20'); + + default: + return type; + } +} diff --git a/libs/governance/src/lib/proposal-details-page.tsx b/libs/governance/src/lib/proposal-details-page.tsx index 69fe7efc5..1cde697e1 100644 --- a/libs/governance/src/lib/proposal-details-page.tsx +++ b/libs/governance/src/lib/proposal-details-page.tsx @@ -12,6 +12,7 @@ import { Proposal, SoftwareUpgradeProposalContent, } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import Markdown from 'marked-react'; import Link from 'next/link'; @@ -67,52 +68,19 @@ import { } from '@haqq/shell-ui-kit/server'; import { ParameterChangeProposalDetails } from './components/parameter-change-proposal'; import { SoftwareUpgradeProposalDetails } from './components/software-upgrade-proposal'; - -const enum ProposalTypes { - Text = '/cosmos.gov.v1beta1.TextProposal', - SoftwareUpgrade = '/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal', - CancelSoftwareUpgrade = '/cosmos.upgrade.v1beta1.CancelSoftwareUpgradeProposal', - ParameterChange = '/cosmos.params.v1beta1.ParameterChangeProposal', - ClientUpdate = '/ibc.core.client.v1.ClientUpdateProposal', - RegisterCoin = '/evmos.erc20.v1.RegisterCoinProposal', - RegisterERC20 = '/evmos.erc20.v1.RegisterERC20Proposal', -} - -export function getProposalTypeText(type: string) { - switch (type) { - case ProposalTypes.Text: - return 'Text'; - - case ProposalTypes.SoftwareUpgrade: - return 'Software upgrade'; - - case ProposalTypes.CancelSoftwareUpgrade: - return 'Cancel software upgrade'; - - case ProposalTypes.ClientUpdate: - return 'Client update'; - - case ProposalTypes.ParameterChange: - return 'Parameter change'; - - case ProposalTypes.RegisterCoin: - return 'Register coin'; - - case ProposalTypes.RegisterERC20: - return 'Register ERC20'; - - default: - return type; - } -} +import { + ProposalTypes, + useGetProposalTypeText, +} from './hooks/useGetProposalTypeText'; function ShowDateToggleButton({ onClick }: { onClick: () => void }) { + const { t } = useTranslate('governance'); return (
- {getProposalTypeText(proposalDetails.content['@type'])} + {useGetProposalTypeText(proposalDetails.content['@type'])}
@@ -430,7 +401,7 @@ export function ProposalDetailsComponent({
- Info + {t('info', 'Info', { ns: 'common' })}
@@ -443,7 +414,11 @@ export function ProposalDetailsComponent({
*/}
- + {formatNumber(totalDeposit)}{' '} {symbol.toLocaleUpperCase()} @@ -451,7 +426,7 @@ export function ProposalDetailsComponent({
- Description + {t('description', 'Description', { ns: 'common' })}
- Dates + {t('dates', 'Dates', { ns: 'governance' })}
- + {formatDate(new Date(proposalDetails.submit_time))} - + {formatDate(new Date(proposalDetails.deposit_end_time))} {proposalDetails.status !== ProposalStatusEnum.Deposit && ( - + {formatDate( new Date(proposalDetails.voting_start_time), )} - + {formatDate( new Date(proposalDetails.voting_end_time), )} @@ -573,7 +564,9 @@ export function ProposalDetailsComponent({ - Created at (gmt) + {t('created-at', 'Created at (GMT)', { + ns: 'governance', + })} @@ -594,7 +587,13 @@ export function ProposalDetailsComponent({ - Deposit end (gmt) + {t( + 'deposit-end-gmt', + 'Deposit end (GMT)', + { + ns: 'governance', + }, + )} @@ -617,7 +616,9 @@ export function ProposalDetailsComponent({ - Vote start (gmt) + {t('vote-start', 'Vote start (GMT)', { + ns: 'governance', + })} @@ -640,7 +641,9 @@ export function ProposalDetailsComponent({ - Vote end (gmt) + {t('vote-end', 'Vote end (GMT)', { + ns: 'governance', + })} @@ -694,7 +697,9 @@ export function ProposalDetailsComponent({ )} {proposalDetails.status === ProposalStatusEnum.Voting && @@ -702,7 +707,9 @@ export function ProposalDetailsComponent({ )}
@@ -801,17 +808,18 @@ function ProposalTurnoutQuorum({ quorum: string; status: ProposalStatusEnum; }) { + const { t } = useTranslate('governance'); return (
- You should connect wallet first + {t('connect-wallet-message', 'You should connect wallet first')}
@@ -891,6 +900,7 @@ function ProposalActionsMobile({ } function ProposalInfo({ proposalId }: { proposalId: string }) { + const { t } = useTranslate('governance'); const { data: proposalDetails, isFetched } = useProposalDetailsQuery(proposalId); const { data: proposalTally } = useProposalTallyQuery(proposalId); @@ -907,7 +917,7 @@ function ProposalInfo({ proposalId }: { proposalId: string }) {
- Fetching proposal details + {t('fetching-proposal-details', 'Fetching proposal details')}
) : ( @@ -922,12 +932,13 @@ function ProposalInfo({ proposalId }: { proposalId: string }) { } export function ProposalDetailsPage({ proposalId }: { proposalId: string }) { + const { t } = useTranslate('common'); return (
- Governance + {t('governance', 'Governance')}
@@ -944,6 +955,7 @@ export function VoteActions({ proposalId: number; userVote?: string | null; }) { + const { t } = useTranslate(); const { vote, getVoteEstimatedFee } = useProposalActions(); const toast = useToast(); const { executeIfNetworkSupported } = useNetworkAwareAction(); @@ -969,7 +981,11 @@ export function VoteActions({ ); await toast.promise(votePromise, { - loading: Vote in progress, + loading: ( + + {t('vote-in-progress', 'Vote in progress', { ns: 'governance' })} + + ), success: (tx) => { console.log('Vote successful', { tx }); const txHash = tx?.txhash; @@ -977,7 +993,11 @@ export function VoteActions({ return (
-
Your vote will be counted!!!
+
+ {t('vote-will-count', 'Your vote will be counted!!!', { + ns: 'governance', + })} +
{ console.error(error); - return For some reason your vote failed.; + return ( + + {t('vote-fail-error', 'For some reason your vote failed.', { + ns: 'governance', + })} + + ); }, }); posthog.capture('vote success', { chainId }); @@ -1012,6 +1038,7 @@ export function VoteActions({ memo, posthog, proposalId, + t, toast, vote, ], @@ -1024,9 +1051,15 @@ export function VoteActions({ return (
- Cast your vote + + {t('cast-vote', 'Cast your vote', { ns: 'governance' })} +
- You can change your vote while the voting is in progress + {t( + 'change-vote-message', + 'You can change your vote while the voting is in progress', + { ns: 'governance' }, + )}
@@ -1038,7 +1071,7 @@ export function VoteActions({ setMemoVisible(true); }} > - Add memo + {t('add-memo', 'Add memo', { ns: 'common' })}
) : ( @@ -1055,7 +1088,7 @@ export function VoteActions({ 'rounded-[6px] bg-[#252528]', 'disabled:cursor-not-allowed', )} - placeholder="Add your memo" + placeholder={t('add-your-memo', 'Add your memo', { ns: 'common' })} autoFocus />
@@ -1075,7 +1108,7 @@ export function VoteActions({ voteOptionFromJSON(userVote) === VoteOption.VOTE_OPTION_YES } > - Yes + {t('vote-option-yes', 'Yes', { ns: 'common' })}
@@ -1091,7 +1124,7 @@ export function VoteActions({ voteOptionFromJSON(userVote) === VoteOption.VOTE_OPTION_NO } > - No + {t('vote-option-no', 'No', { ns: 'common' })}
@@ -1107,7 +1140,7 @@ export function VoteActions({ voteOptionFromJSON(userVote) === VoteOption.VOTE_OPTION_ABSTAIN } > - Abstain + {t('vote-option-abstain', 'Abstain', { ns: 'common' })}
@@ -1124,7 +1157,7 @@ export function VoteActions({ VoteOption.VOTE_OPTION_NO_WITH_VETO } > - Veto + {t('vote-option-no-with-veto', 'No with veto', { ns: 'common' })}
@@ -1141,6 +1174,7 @@ export function DepositActionsDesktop({ onDepositSubmit: (depositAmount: number) => void; isConnected: boolean; }) { + const { t } = useTranslate(); const [depositAmount, setDepositAmount] = useState( undefined, ); @@ -1156,10 +1190,16 @@ export function DepositActionsDesktop({
- Enter the amount you want to deposit + {t('enter-deposit-message', 'Enter the amount you want to deposit', { + ns: 'governance', + })}
- You balance: {balance.toLocaleString()} {symbol.toLocaleUpperCase()} + {t('your-balance', 'Your balance: {balance} {symbol}', { + ns: 'governance', + balance: balance.toLocaleString(), + symbol: symbol.toLocaleUpperCase(), + })}
@@ -1179,7 +1219,7 @@ export function DepositActionsDesktop({ !isConnected || (depositAmount && depositAmount === 0), )} > - Deposit + {t('deposit', 'Deposit', { ns: 'common' })}
@@ -1197,11 +1237,12 @@ export function DepositInput({ value: number | undefined; disabled?: boolean; }) { + const { t } = useTranslate('common'); return (
- If the proposal does not collect the required number of deposits in a - certain time, it will reject + {t( + 'proposal-deposit-alert', + 'If the proposal does not collect the required number of deposits in a certain time, it will reject', + )} ); } diff --git a/libs/governance/src/lib/proposal-list-page.tsx b/libs/governance/src/lib/proposal-list-page.tsx index 5c96bb157..3da4ce8d7 100644 --- a/libs/governance/src/lib/proposal-list-page.tsx +++ b/libs/governance/src/lib/proposal-list-page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useMemo } from 'react'; import { ProposalStatus } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { useAddress, @@ -14,6 +15,7 @@ import { Container, SpinnerLoader } from '@haqq/shell-ui-kit/server'; import { ProposalListCard } from './components/proposal-list-card'; export function ProposalListPage() { + const { t } = useTranslate(); const { data: govParams } = useGovernanceParamsQuery(); const { data: proposalsData } = useProposalListQuery(); const symbol = 'ISLM'; @@ -98,7 +100,7 @@ export function ProposalListPage() {
- Governance + {t('governance', 'Governance', { ns: 'common' })}
@@ -111,7 +113,9 @@ export function ProposalListPage() {
- Fetching proposals + {t('fetching-proposals', 'Fetching proposals', { + ns: 'common', + })}
diff --git a/libs/main/src/lib/components/account-footer-mobile.tsx b/libs/main/src/lib/components/account-footer-mobile.tsx index a73abe88b..f66a57861 100644 --- a/libs/main/src/lib/components/account-footer-mobile.tsx +++ b/libs/main/src/lib/components/account-footer-mobile.tsx @@ -1,3 +1,4 @@ +import { useTranslate } from '@tolgee/react'; import { useAccount } from 'wagmi'; import { getFormattedAddress, @@ -9,6 +10,7 @@ import { Button, AccountButton } from '@haqq/shell-ui-kit'; import { Container, LogoutIcon } from '@haqq/shell-ui-kit/server'; export function AccountFooterMobile() { + const { t } = useTranslate('common'); const { isConnected } = useAccount(); const { disconnect, openSelectWallet } = useWallet(); const { haqqAddress, ethAddress } = useAddress(); @@ -39,7 +41,7 @@ export function AccountFooterMobile() { ) : (
)} diff --git a/libs/main/src/lib/components/delegation-list.tsx b/libs/main/src/lib/components/delegation-list.tsx index 1b2483016..55c52967c 100644 --- a/libs/main/src/lib/components/delegation-list.tsx +++ b/libs/main/src/lib/components/delegation-list.tsx @@ -4,6 +4,7 @@ import { Validator, DistributionRewardsResponse, } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import { bondStatusFromJSON } from 'cosmjs-types/cosmos/staking/v1beta1/staking'; import Link from 'next/link'; import { formatUnits } from 'viem'; @@ -131,6 +132,7 @@ function mapAndSortValidators( } export function ShellIndexPageDelegationList() { + const { t } = useTranslate(); const { haqqAddress } = useAddress(); const { data: validatorsList, @@ -156,7 +158,11 @@ export function ShellIndexPageDelegationList() { if (!haqqAddress) { return ( -
You should connect wallet first
+
+ {t('connect-wallet-message', 'You should connect wallet first', { + ns: 'common', + })} +
); } @@ -167,22 +173,39 @@ export function ShellIndexPageDelegationList() {
-
Name
+
+ {t('name', 'Name', { ns: 'common' })} +
+
+
+ {t('status', 'Status', { ns: 'common' })} +
+
+ {t('fee', 'Fee', { ns: 'common' })} +
+
+ {t('voting-power', 'Voting power', { ns: 'common' })} +
+
+ {t('staked', 'Staked', { ns: 'common' })} +
+
+ {t('rewards', 'Rewards', { ns: 'common' })}
-
Status
-
Fee
-
Voting power
-
Staked
-
Rewards
{status === 'pending' && (
-
Fetching validators list
+
+ {t('fetching-validators', 'Fetching validators list', { + ns: 'main', + })} +
)} + {/* eslint-disable-next-line i18next/no-literal-string */} {status === 'error' &&

Error: {(error as Error).message}

} {status === 'success' && (
@@ -198,7 +221,15 @@ export function ShellIndexPageDelegationList() { }) ) : (
- You don't have active delegations + + {t( + 'no-delegations', + "You don't have any active delegations", + { + ns: 'main', + }, + )} +
)}
diff --git a/libs/main/src/lib/components/delegations-block.tsx b/libs/main/src/lib/components/delegations-block.tsx index f832e2d4e..54a7732cb 100644 --- a/libs/main/src/lib/components/delegations-block.tsx +++ b/libs/main/src/lib/components/delegations-block.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback } from 'react'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useMediaQuery } from 'usehooks-ts'; @@ -23,6 +24,7 @@ export function DelegationsBlock({ isMobileUserAgent: boolean; seedPhrase: string; }) { + const { t } = useTranslate(); const { totalStaked, status, @@ -57,7 +59,7 @@ export function DelegationsBlock({
- My delegations + {t('my-delegations', 'My delegations', { ns: 'common' })}
@@ -74,6 +76,7 @@ export function DelegationsBlock({ )}
*/} + {/* eslint-disable-next-line i18next/no-literal-string */} {status === 'error' &&

Error: {(error as Error).message}

} {status === 'success' && ( @@ -121,10 +124,16 @@ export function DelegationsBlock({ ) : (
- You don't have any active delegations + {t('no-delegations', "You don't have any active delegations", { + ns: 'main', + })}
- Go to Staking + + {t('link-to-staking', 'Go to Staking', { + ns: 'main', + })} +
)} diff --git a/libs/main/src/lib/components/my-account-block.tsx b/libs/main/src/lib/components/my-account-block.tsx index 0a2d536bf..24b2b7fe3 100644 --- a/libs/main/src/lib/components/my-account-block.tsx +++ b/libs/main/src/lib/components/my-account-block.tsx @@ -1,4 +1,5 @@ import { ReactNode, useCallback, useMemo, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import Link from 'next/link'; // import { useMediaQuery } from 'react-responsive'; @@ -71,20 +72,21 @@ function MyAccountAmountBlock({ } export function MyAccountBlock() { + const { t } = useTranslate('common'); const { ethAddress, haqqAddress } = useAddress(); const { openSelectWallet } = useWallet(); return !ethAddress || !haqqAddress ? (
- You should connect wallet first + {t('connect-wallet-message', 'You should connect wallet first')}
) : ( @@ -99,6 +101,7 @@ function MyAccountConnected({ ethAddress: Hex; haqqAddress: string; }) { + const { t } = useTranslate(); const [isEthAddressCopy, setEthAddressCopy] = useState(false); const [isHaqqAddressCopy, setHaqqAddressCopy] = useState(false); const { copyText } = useClipboard(); @@ -188,11 +191,11 @@ function MyAccountConnected({
- My account + {t('my-account', 'My account', { ns: 'common' })} - Go to Staking + {t('link-to-staking', 'Go to Staking', { ns: 'main' })}
@@ -200,7 +203,7 @@ function MyAccountConnected({
- Balance + {t('balance', 'Balance', { ns: 'common' })}
@@ -221,7 +224,10 @@ function MyAccountConnected({ )} > - Available for staking:{' '} + {t('available-staking', 'Available for staking', { + ns: 'main', + })} + {': '} {formatNumber(balances.available)} @@ -231,7 +237,12 @@ function MyAccountConnected({ @@ -269,28 +280,31 @@ function MyAccountConnected({
- Available: {formatNumber(balances.availableForStake)} + {t('available-for-stake', 'Available: {amount}', { + amount: formatNumber(balances.availableForStake), + })}
@@ -405,10 +425,11 @@ function StakingBalancePopup({
- Locked:{' '} - {isLiquidStaking - ? formatNumber(stIslmBalance + balances.locked) - : formatNumber(balances.locked)} + {t('locked', 'Locked: {amount}', { + amount: isLiquidStaking + ? formatNumber(stIslmBalance + balances.locked) + : formatNumber(balances.locked), + })}
diff --git a/libs/main/src/lib/components/proposal-list-block.tsx b/libs/main/src/lib/components/proposal-list-block.tsx index 64e8d7876..42de965a8 100644 --- a/libs/main/src/lib/components/proposal-list-block.tsx +++ b/libs/main/src/lib/components/proposal-list-block.tsx @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import { ProposalStatus } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { ProposalListCard } from '@haqq/shell-governance'; import { @@ -16,6 +17,7 @@ import { } from '@haqq/shell-ui-kit/server'; export function ProposalListBlock() { + const { t } = useTranslate(); const { data: govParams } = useGovernanceParamsQuery(); const { data: proposalsData, isFetching } = useProposalListQuery(); const symbol = 'ISLM'; @@ -75,11 +77,11 @@ export function ProposalListBlock() {
- Latest proposals + {t('latest-proposals', 'Latest proposals', { ns: 'main' })} - Go to Governance + {t('link-to-governance', 'Go to Governance', { ns: 'main' })}
@@ -88,7 +90,7 @@ export function ProposalListBlock() {
- Fetching proposals + {t('fetching-proposals', 'Fetching proposals', { ns: 'common' })}
) : ( diff --git a/libs/main/src/lib/components/statistics-block.tsx b/libs/main/src/lib/components/statistics-block.tsx index 855286f74..5ce3b089a 100644 --- a/libs/main/src/lib/components/statistics-block.tsx +++ b/libs/main/src/lib/components/statistics-block.tsx @@ -1,8 +1,10 @@ import { useMemo } from 'react'; +import { T, useTranslate } from '@tolgee/react'; import { useChainStatsQuery } from '@haqq/shell-shared'; import { formatNumber, formatPercents } from '@haqq/shell-ui-kit/server'; export function StatisticsBlock() { + const { t } = useTranslate('main'); const { data: chainStats, isFetching, isFetched } = useChainStatsQuery(); const { @@ -38,7 +40,7 @@ export function StatisticsBlock() {
- Total supply + {t('total-supply', 'Total supply')}
{!isFetching && ( @@ -51,7 +53,7 @@ export function StatisticsBlock() {
- Total staked ({formatPercents(stakeRatio)}%) + {t('total-staked', 'Total staked')} ({formatPercents(stakeRatio)}%)
{!isFetching && ( @@ -64,7 +66,7 @@ export function StatisticsBlock() {
- Accounts + {t('accounts', 'Accounts')}
{!isFetching &&
{totalAccounts}
} @@ -72,13 +74,21 @@ export function StatisticsBlock() {
- Active validators + {t('active-validators', 'Active validators')}
{!isFetching && (
- {valsActive} -  out of {valsTotal} + , + }} + />
)}
diff --git a/libs/main/src/lib/main-page.tsx b/libs/main/src/lib/main-page.tsx index 7ecfefc39..2d01b32d6 100644 --- a/libs/main/src/lib/main-page.tsx +++ b/libs/main/src/lib/main-page.tsx @@ -1,4 +1,5 @@ 'use client'; +import { useTranslate } from '@tolgee/react'; import { useMediaQuery } from 'usehooks-ts'; import { useWallet } from '@haqq/shell-shared'; import { Container } from '@haqq/shell-ui-kit/server'; @@ -30,6 +31,7 @@ export function MainPage({ isMobileUserAgent: boolean; seedPhrase: string; }) { + const { t } = useTranslate('main'); const { isHaqqWallet } = useWallet(); const isTablet = useMediaQuery('(max-width: 1023px)'); @@ -40,7 +42,7 @@ export function MainPage({
{!isHaqqWallet && (
- Shell + {t('shell-title', 'Shell')}
)} diff --git a/libs/staking/src/lib/components/delegate-modal-hooked.tsx b/libs/staking/src/lib/components/delegate-modal-hooked.tsx index d3e002df6..797b1cd95 100644 --- a/libs/staking/src/lib/components/delegate-modal-hooked.tsx +++ b/libs/staking/src/lib/components/delegate-modal-hooked.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { usePostHog } from 'posthog-js/react'; import { useDebounceValue } from 'usehooks-ts'; @@ -45,6 +46,7 @@ export function DelegateModalHooked({ unboundingTime, validatorCommission, }: DelegateModalProps) { + const { t } = useTranslate('staking'); const { delegate, getDelegateEstimatedFee } = useStakingActions(); const [delegateAmount, setDelegateAmount] = useState( undefined, @@ -96,7 +98,11 @@ export function DelegateModalHooked({ await toast.promise( delegationPromise, { - loading: Delegation in progress, + loading: ( + + {t('delegation-progress', 'Delegation in progress')} + + ), success: (tx) => { console.log('Delegation successful', { tx }); const txHash = tx?.txhash; @@ -115,7 +121,7 @@ export function DelegateModalHooked({ return (
-
Delegation successful
+
{t('delegation-success', 'Delegation successful')}
{ @@ -163,13 +165,23 @@ export function DelegateModal({ const amountHint = useMemo(() => { if (amountError === 'min') { - return Bellow minimal value; + return ( + + {t('amount-error-min', 'Bellow minimal value', { ns: 'common' })} + + ); } else if (amountError === 'max') { - return More than you have; + return ( + + {t('amount-error-more-than-have', 'More than you have', { + ns: 'common', + })} + + ); } return undefined; - }, [amountError]); + }, [amountError, t]); return ( @@ -183,25 +195,32 @@ export function DelegateModal({
- Delegate + {t('delegate', 'Delegate', { ns: 'common' })} - {`Attention! If in the future you want to withdraw the staked funds, it will take ${unboundingTime} ${unboundingTime === 1 ? 'day' : 'days'}`} + {t( + 'attention-withdrawal-warning', + 'Attention! If in the future you want to withdraw the staked funds, it will take {count} day{count, plural, one {} other {s}}', + { + ns: 'staking', + count: unboundingTime, + }, + )}
@@ -227,7 +246,7 @@ export function DelegateModal({ setMemoVisible(true); }} > - Add memo + {t('add-memo', 'Add memo', { ns: 'common' })}
) : ( @@ -245,14 +264,18 @@ export function DelegateModal({ 'px-[16px] py-[12px] text-[14px] font-[500] leading-[22px]', 'bg-[#E7E7E7]', )} - placeholder="Add your memo" + placeholder={t('memo-placeholder', 'Add your memo', { + ns: 'staking', + })} />
)}
@@ -265,7 +288,9 @@ export function DelegateModal({ className="w-full" disabled={isDisabled} > - Confirm delegation + {t('confirm-delegation', 'Confirm delegation', { + ns: 'common', + })}
diff --git a/libs/staking/src/lib/components/redelegate-modal-hooked.tsx b/libs/staking/src/lib/components/redelegate-modal-hooked.tsx index 0ae4129eb..d948ab0a0 100644 --- a/libs/staking/src/lib/components/redelegate-modal-hooked.tsx +++ b/libs/staking/src/lib/components/redelegate-modal-hooked.tsx @@ -1,6 +1,7 @@ 'use client'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Validator } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { usePostHog } from 'posthog-js/react'; import { useDebounceValue } from 'usehooks-ts'; @@ -48,6 +49,7 @@ export function RedelegateModalHooked({ validatorsList, balance, }: RedelegateModalProps) { + const { t } = useTranslate('staking'); const { haqqAddress, ethAddress } = useAddress(); const { data: redelegationValidatorAmount } = useRedelegationValidatorAmount( @@ -107,7 +109,11 @@ export function RedelegateModalHooked({ await toast.promise( redelegationPromise, { - loading: Redelegate in progress, + loading: ( + + {t('redelegate-progress', 'Redelegate in progress')} + + ), success: (tx) => { console.log('Redelegation successful', { tx }); const txHash = tx?.txhash; @@ -126,7 +132,9 @@ export function RedelegateModalHooked({ return (
-
Redelegation successful
+
+ {t('redelegate-success', 'Redelegation successful')} +
{ @@ -133,14 +135,14 @@ export function RedelegateModal({
- Redelegate + {t('redelegate', 'Redelegate', { ns: 'common' })}
@@ -150,7 +152,9 @@ export function RedelegateModal({
@@ -183,7 +187,7 @@ export function RedelegateModal({ setMemoVisible(true); }} > - Add memo + {t('add-memo', 'Add memo', { ns: 'common' })}
) : ( @@ -201,14 +205,18 @@ export function RedelegateModal({ 'px-[16px] py-[12px] text-[14px] font-[500] leading-[22px]', 'bg-[#E7E7E7]', )} - placeholder="Add your memo" + placeholder={t('memo-placeholder', 'Add your memo', { + ns: 'staking', + })} />
)}
@@ -221,7 +229,9 @@ export function RedelegateModal({ className="w-full" disabled={isDisabled} > - Confirm redelegation + {t('confirm-redelegation', 'Confirm redelegation', { + ns: 'staking', + })}
diff --git a/libs/staking/src/lib/components/staking-info.tsx b/libs/staking/src/lib/components/staking-info.tsx index 4148fb038..d02a3b27b 100644 --- a/libs/staking/src/lib/components/staking-info.tsx +++ b/libs/staking/src/lib/components/staking-info.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import Link from 'next/link'; import { usePostHog } from 'posthog-js/react'; @@ -37,6 +38,7 @@ import { StakingStatsDesktop, StakingStatsMobile } from './staking-stats'; import { shouldUsePrecompile } from '../constants'; function useStakingStats() { + const { t } = useTranslate('staking'); const [delegatedValsAddrs, setDelegatedValsAddrs] = useState>( [], ); @@ -95,7 +97,11 @@ function useStakingStats() { }); await toast.promise(claimAllRewardPromise, { - loading: Rewards claim in progress, + loading: ( + + {t('rewards-progress', 'Rewards claim in progress')} + + ), success: (tx) => { console.log('Rewards claimed', { tx }); const txHash = tx?.txhash; @@ -103,7 +109,7 @@ function useStakingStats() { return (
-
Rewards claimed
+
{t('rewards-claimed', 'Rewards claimed')}
- You should connect wallet first + {t('connect-wallet-message', 'You should connect wallet first')}
diff --git a/libs/staking/src/lib/components/staking-stats.tsx b/libs/staking/src/lib/components/staking-stats.tsx index fa49c28de..0153959bb 100644 --- a/libs/staking/src/lib/components/staking-stats.tsx +++ b/libs/staking/src/lib/components/staking-stats.tsx @@ -1,3 +1,4 @@ +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { Button, Tooltip } from '@haqq/shell-ui-kit'; import { @@ -58,12 +59,13 @@ export function StakingStatsDesktop({ onRewardsClaim, isRewardsPending = false, }: StakingStatsProps) { + const { t } = useTranslate(); return (
- Regular staking + {t('regular-staking', 'Regular staking', { ns: 'staking' })}
@@ -72,7 +74,7 @@ export function StakingStatsDesktop({
@@ -80,7 +82,7 @@ export function StakingStatsDesktop({
@@ -88,7 +90,7 @@ export function StakingStatsDesktop({
@@ -96,7 +98,7 @@ export function StakingStatsDesktop({
@@ -118,7 +124,7 @@ export function StakingStatsDesktop({ variant={2} isLoading={isRewardsPending} > - Claim all rewards + {t('claim-all-rewards', 'Claim all rewards', { ns: 'common' })}
@@ -173,33 +179,34 @@ export function StakingStatsMobile({ unbounded, isRewardsPending = false, }: StakingStatsProps) { + const { t } = useTranslate(); return (
- Regular staking + {t('regular-staking', 'Regular staking', { ns: 'staking' })}
@@ -222,7 +233,7 @@ export function StakingStatsMobile({ disabled={Number.parseFloat(rewards) < MIN_REWARDS_TO_CLAIM} data-attr="get-rewards" > - Get rewards + {t('get-rewards', 'Get rewards', { ns: 'staking' })}
diff --git a/libs/staking/src/lib/components/stride/liquid-staking-delegate-modal-hooked.tsx b/libs/staking/src/lib/components/stride/liquid-staking-delegate-modal-hooked.tsx index ce23d9edd..385dc8fec 100644 --- a/libs/staking/src/lib/components/stride/liquid-staking-delegate-modal-hooked.tsx +++ b/libs/staking/src/lib/components/stride/liquid-staking-delegate-modal-hooked.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { usePostHog } from 'posthog-js/react'; import { useDebounceValue } from 'usehooks-ts'; @@ -37,6 +38,7 @@ export function LiquidStakingDelegateModalHooked({ balance, unboundingTime, }: LiquidStakingDelegateModalProps) { + const { t } = useTranslate('staking'); const [delegateAmount, setDelegateAmount] = useState( undefined, ); @@ -83,13 +85,21 @@ export function LiquidStakingDelegateModalHooked({ await toast.promise( delegationPromise, { - loading: Delegation in progress, + loading: ( + + {t('delegation-progress', 'Delegation in progress')} + + ), success: (tx) => { console.log('Delegation successful', { tx }); const txHash = tx?.txhash; if (!txHash) { - return Delegation declined; + return ( + + {t('delegation-declined', 'Delegation declined')} + + ); } posthog.capture('delegate success', { @@ -106,7 +116,7 @@ export function LiquidStakingDelegateModalHooked({ return (
-
Delegation successful
+
{t('delegation-success', 'Delegation successful')}
{ onChange(Math.floor(balance)); }, [balance, onChange]); @@ -158,13 +160,23 @@ export function LiquidStakingDelegateModal({ const amountHint = useMemo(() => { if (amountError === 'min') { - return Bellow minimal value; + return ( + + {t('amount-error-min', 'Bellow minimal value', { ns: 'common' })} + + ); } else if (amountError === 'max') { - return More than you have; + return ( + + {t('amount-error-more-than-have', 'More than you have', { + ns: 'common', + })} + + ); } return undefined; - }, [amountError]); + }, [amountError, t]); const { stIslmFormIslm } = useStIslmFormIslm(delegateAmount || 0); @@ -186,26 +198,33 @@ export function LiquidStakingDelegateModal({
- Delegate + {t('delegate', 'Delegate', { ns: 'common' })} - {`Attention! If in the future you want to withdraw the staked funds, it will take ${unboundingTime} ${unboundingTime === 1 ? 'day' : 'days'}`} + {t( + 'attention-withdrawal-warning', + 'Attention! If in the future you want to withdraw the staked funds, it will take {count} day{count, plural, one {} other {s}}', + { + ns: 'staking', + count: unboundingTime, + }, + )}
@@ -224,10 +243,18 @@ export function LiquidStakingDelegateModal({ - Stride address is required to delegate + {t( + 'stride-address-required', + 'Stride address is required to delegate', + { ns: 'staking' }, + )} } /> @@ -235,9 +262,10 @@ export function LiquidStakingDelegateModal({
- What you'll get: + {t('you-will-get', "What you'll get:", { ns: 'staking' })}
+ {/* eslint-disable-next-line i18next/no-literal-string */} {formatNumber(stIslmFormIslm)} stISLM
@@ -245,9 +273,12 @@ export function LiquidStakingDelegateModal({ {annualizedYield ? (
- Annual percentage yield + {t('annualized-yield', 'Annual percentage yield', { + ns: 'staking', + })}
+ {/* eslint-disable-next-line i18next/no-literal-string */} {formatNumber(annualizedYield)} stISLM
@@ -261,7 +292,9 @@ export function LiquidStakingDelegateModal({ className="w-full" disabled={isDisabled} > - Confirm delegation + {t('confirm-delegation', 'Confirm delegation', { + ns: 'common', + })}
diff --git a/libs/staking/src/lib/components/stride/liquid-staking-undelegate-modal-hooked.tsx b/libs/staking/src/lib/components/stride/liquid-staking-undelegate-modal-hooked.tsx index 987b5d63e..d68fd215c 100644 --- a/libs/staking/src/lib/components/stride/liquid-staking-undelegate-modal-hooked.tsx +++ b/libs/staking/src/lib/components/stride/liquid-staking-undelegate-modal-hooked.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { usePostHog } from 'posthog-js/react'; import { useDebounceValue } from 'usehooks-ts'; @@ -39,6 +40,7 @@ export function LiquidStakingUndelegateModalHooked({ delegation, unboundingTime, }: LiquidStakingUndelegateModalProps) { + const { t } = useTranslate('staking'); const { undelegate, setStrideAddress, strideAddress } = useLiquidStakingUndelegate(); const [undelegateAmount, setUndelegateAmount] = useState( @@ -80,13 +82,21 @@ export function LiquidStakingUndelegateModalHooked({ await toast.promise( undelegationPromise, { - loading: Undlegation in progress, + loading: ( + + {t('undelegation-progress', 'Undelegation in progress')} + + ), success: (tx) => { - console.log('Undlegation successful', { tx }); + console.log('Undelegation successful', { tx }); const txHash = tx?.txhash; if (!txHash) { - return Undelegation declined; + return ( + + {t('undelegation-declined', 'Undelegation declined')} + + ); } posthog.capture('undelegate success', { @@ -103,7 +113,9 @@ export function LiquidStakingUndelegateModalHooked({ return (
-
Undelegation successful
+
+ {t('undelegation-success', 'Undelegation successful')} +
{ onChange(delegation); }, [delegation, onChange]); @@ -72,15 +74,23 @@ export function LiquidStakingUndelegateModal({ const amountHint = useMemo(() => { if (amountError === 'min') { - return Bellow minimal value; + return ( + + {t('amount-error-min', 'Bellow minimal value', { ns: 'common' })} + + ); } else if (amountError === 'max') { return ( - More than your delegation + + {t('amount-error-more-than-delegation', 'More than your delegation', { + ns: 'staking', + })} + ); } return undefined; - }, [amountError]); + }, [amountError, t]); return ( @@ -94,7 +104,7 @@ export function LiquidStakingUndelegateModal({
- Undelegate + {t('undelegate', 'Undelegate', { ns: 'common' })} - {`The funds will be undelegate within ${unboundingTime} day`} + {t( + 'funds-undelegated-in-days', + 'The funds will be undelegated within {count, plural, one {# day} other {# days}}', + { + ns: 'staking', + count: unboundingTime, + }, + )}
@@ -130,10 +147,18 @@ export function LiquidStakingUndelegateModal({ - Stride address is required to undelegate + {t( + 'stride-address-required', + 'Stride address is required to undelegate', + { ns: 'staking' }, + )} } /> @@ -145,7 +170,9 @@ export function LiquidStakingUndelegateModal({ className="w-full" disabled={isDisabled} > - Confirm undelegation + {t('confirm-undelegation', 'Confirm undelegation', { + ns: 'staking', + })}
diff --git a/libs/staking/src/lib/components/stride/statistics/stride-stats.tsx b/libs/staking/src/lib/components/stride/statistics/stride-stats.tsx index 2ee16d41a..ef0ae3113 100644 --- a/libs/staking/src/lib/components/stride/statistics/stride-stats.tsx +++ b/libs/staking/src/lib/components/stride/statistics/stride-stats.tsx @@ -1,4 +1,5 @@ import { useCallback, useMemo } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { useRouter } from 'next/navigation'; import { useMediaQuery } from 'usehooks-ts'; @@ -118,6 +119,7 @@ function StrideStatsDesktop({ stIslmBalance: number; islmAmountFromStIslm: number; }) { + const { t } = useTranslate(); const { handleDelegateContinue, handleUndelegateContinue } = useHandleDelegateContinue(); @@ -126,7 +128,7 @@ function StrideStatsDesktop({
- Liquid staking + {t('liquid-staking', 'Liquid staking', { ns: 'staking' })}
@@ -135,7 +137,7 @@ function StrideStatsDesktop({
- Delegate + {t('delegate', 'Delegate', { ns: 'common' })}
@@ -184,7 +188,7 @@ function StrideStatsDesktop({ handleUndelegateContinue(); }} > - Undelegate + {t('undelegate', 'Undelegate', { ns: 'common' })}
@@ -203,6 +207,7 @@ function StrideStatsMobile({ stIslmBalance: number; islmAmountFromStIslm: number; }) { + const { t } = useTranslate(); const { handleDelegateContinue, handleUndelegateContinue } = useHandleDelegateContinue(); @@ -211,25 +216,27 @@ function StrideStatsMobile({
- Liquid staking + {t('liquid-staking', 'Liquid staking', { ns: 'staking' })}
- Delegate + {t('delegate', 'Delegate', { ns: 'common' })}
diff --git a/libs/staking/src/lib/components/undelegate-modal-hooked.tsx b/libs/staking/src/lib/components/undelegate-modal-hooked.tsx index 1a212d6e5..283c6617e 100644 --- a/libs/staking/src/lib/components/undelegate-modal-hooked.tsx +++ b/libs/staking/src/lib/components/undelegate-modal-hooked.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslate } from '@tolgee/react'; import Link from 'next/link'; import { usePostHog } from 'posthog-js/react'; import { useDebounceValue } from 'usehooks-ts'; @@ -43,6 +44,7 @@ export function UndelegateModalHooked({ unboundingTime, validatorAddress, }: UndelegateModalProps) { + const { t } = useTranslate('staking'); const { undelegate, getUndelegateEstimatedFee } = useStakingActions(); const [undelegateAmount, setUndelegateAmount] = useState( undefined, @@ -94,7 +96,11 @@ export function UndelegateModalHooked({ await toast.promise( undelegationPromise, { - loading: Undlegation in progress, + loading: ( + + {t('undelegation-progress', 'Undelegation in progress')} + + ), success: (tx) => { console.log('Undlegation successful', { tx }); const txHash = tx?.txhash; @@ -112,7 +118,9 @@ export function UndelegateModalHooked({ return (
-
Undelegation successful
+
+ {t('undelegation-success', 'Undelegation successful')} +
{ @@ -79,15 +81,23 @@ export function UndelegateModal({ const amountHint = useMemo(() => { if (amountError === 'min') { - return Bellow minimal value; + return ( + + {t('amount-error-min', 'Bellow minimal value', { ns: 'common' })} + + ); } else if (amountError === 'max') { return ( - More than your delegation + + {t('amount-error-more-than-delegation', 'More than your delegation', { + ns: 'staking', + })} + ); } return undefined; - }, [amountError]); + }, [amountError, t]); return ( @@ -101,7 +111,7 @@ export function UndelegateModal({
- Undelegate + {t('undelegate', 'Undelegate', { ns: 'common' })} - {`The funds will be undelegate within ${unboundingTime} day`} + {t( + 'funds-undelegated-in-days', + 'The funds will be undelegated within {count, plural, one {# day} other {# days}}', + { + ns: 'staking', + count: unboundingTime, + }, + )}
@@ -144,7 +161,7 @@ export function UndelegateModal({ setMemoVisible(true); }} > - Add memo + {t('add-memo', 'Add memo', { ns: 'common' })}
) : ( @@ -162,14 +179,18 @@ export function UndelegateModal({ 'px-[16px] py-[12px] text-[14px] font-[500] leading-[22px]', 'bg-[#E7E7E7]', )} - placeholder="Add your memo" + placeholder={t('memo-placeholder', 'Add your memo', { + ns: 'staking', + })} />
)}
@@ -182,7 +203,9 @@ export function UndelegateModal({ className="w-full" disabled={isDisabled} > - Confirm undelegation + {t('confirm-undelegation', 'Confirm undelegation', { + ns: 'staking', + })}
diff --git a/libs/staking/src/lib/components/validator-block-mobile.tsx b/libs/staking/src/lib/components/validator-block-mobile.tsx index 002dd926a..ac4341fa9 100644 --- a/libs/staking/src/lib/components/validator-block-mobile.tsx +++ b/libs/staking/src/lib/components/validator-block-mobile.tsx @@ -1,4 +1,5 @@ import { PropsWithChildren } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { Button, Tooltip } from '@haqq/shell-ui-kit'; import { @@ -77,42 +78,54 @@ export function ValidatorBlockMobileComponent({ isRewardPending = false, minRewardsToClaim = 1, }: ValidatorBlockMobileProps) { + const { t } = useTranslate(); return (
- Validator + {t('validator', 'Validator', { ns: 'staking' })}
{isWarningShown && (
- While the validator is inactive, you will not be able to receive a - reward. + {t( + 'validator-inactive-warning', + 'While the validator is inactive, you will not be able to receive a reward.', + { ns: 'staking' }, + )}
)}
- My delegation + + {t('my-delegation', 'My delegation', { ns: 'staking' })} + {formatNumber(delegation)} {symbol.toLocaleUpperCase()}
{undelegate && undelegate > 0 && (
- Undelegate in process + + {t('undelegate-process', 'Undelegate in process', { + ns: 'staking', + })} + {formatNumber(undelegate)} {symbol.toLocaleUpperCase()}
)}
- My rewards + + {t('my-rewards', 'My rewards', { ns: 'staking' })} + {formatNumber(rewards)} {symbol.toLocaleUpperCase()} @@ -129,7 +142,7 @@ export function ValidatorBlockMobileComponent({ disabled={isDelegateDisabled} data-attr="delegate" > - Delegate + {t('delegate', 'Delegate', { ns: 'common' })}
@@ -140,7 +153,7 @@ export function ValidatorBlockMobileComponent({ disabled={isUndelegateDisabled} data-attr="undelegate" > - Undelegate + {t('undelegate', 'Undelegate', { ns: 'common' })}
@@ -152,14 +165,18 @@ export function ValidatorBlockMobileComponent({ disabled={isRedelegateDisabled} data-attr="redelegate" > - Redelegate + {t('redelegate', 'Redelegate', { ns: 'common' })}
@@ -171,7 +188,7 @@ export function ValidatorBlockMobileComponent({ isLoading={isRewardPending} data-attr="get-my-rewards" > - Get my rewards + {t('get-my-rewards', 'Get my rewards', { ns: 'staking' })}
diff --git a/libs/staking/src/lib/components/validator-info.tsx b/libs/staking/src/lib/components/validator-info.tsx index 05048bf36..b455a59d4 100644 --- a/libs/staking/src/lib/components/validator-info.tsx +++ b/libs/staking/src/lib/components/validator-info.tsx @@ -1,6 +1,7 @@ 'use client'; import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; import type { Validator } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import Markdown from 'marked-react'; import dynamic from 'next/dynamic'; @@ -137,28 +138,29 @@ function CommissionCardInnerBlock({ } function CommissionCard({ commission }: CommissionCardProps) { + const { t } = useTranslate('staking'); return (
- Commission + {t('commission', 'Commission')}
@@ -182,6 +184,7 @@ export function ValidatorInfoComponent({ isRewardPending, isRewardsPending, }: ValidatorInfoComponentProps) { + const { t } = useTranslate(); const [isHaqqAddressCopy, setHaqqAddressCopy] = useState(false); const { copyText } = useClipboard(); const isDesktop = useMediaQuery('(min-width: 1024px)', { @@ -250,7 +253,7 @@ export function ValidatorInfoComponent({
- Info + {t('info', 'Info', { ns: 'common' })}
@@ -264,7 +267,9 @@ export function ValidatorInfoComponent({ target="_blank" rel="noreferrer noreferrer" > - Website + + {t('website', 'Website', { ns: 'staking' })} + )} @@ -272,7 +277,9 @@ export function ValidatorInfoComponent({ - E-mail + + {t('email', 'E-mail', { ns: 'staking' })} + )}
@@ -280,12 +287,18 @@ export function ValidatorInfoComponent({
- + {formatNumber(votingPower)} {symbol.toLocaleUpperCase()}
- + {votingPowerInPercents}
@@ -293,7 +306,7 @@ export function ValidatorInfoComponent({ {validatorInfo.description?.details && (
- Description + {t('description', 'Description', { ns: 'common' })}
)} - +
void; }) { + const { t } = useTranslate('common'); return (
- You should connect wallet first + {t('connect-wallet-message', 'You should connect wallet first')}
@@ -447,6 +462,7 @@ export function ValidatorInfo({ }: { validatorAddress: string; }) { + const { t } = useTranslate('staking'); const { haqqAddress } = useAddress(); const chains = useChains(); const { chain = chains[0] } = useAccount(); @@ -550,7 +566,11 @@ export function ValidatorInfo({ }); await toast.promise(claimRewardPromise, { - loading: Rewards claim in progress, + loading: ( + + {t('rewards-claim-in-progress', 'Rewards claim in progress')} + + ), success: (tx) => { console.log('Rewards claimed', { tx }); const txHash = tx?.txhash; @@ -558,7 +578,7 @@ export function ValidatorInfo({ return (
-
Rewards claimed
+
{t('rewards-claimed', 'Rewards claimed')}
Rewards claim in progress, + loading: ( + + {t('rewards-claim-in-progress', 'Rewards claim in progress')} + + ), success: (tx) => { console.log('All rewards claimed', { tx }); const txHash = tx?.txhash; return (
-
Rewards claimed
+
{t('rewards-claimed', 'Rewards claimed')}
- Explorer link + {t('explorer-link', 'Explorer link')}
); @@ -716,6 +741,7 @@ export function ValidatorInfo({ getTotalRewards, invalidateQueries, posthog, + t, toast, ]); @@ -724,7 +750,7 @@ export function ValidatorInfo({
- Fetching validator information + {t('validator-info-loading', 'Fetching validator information')}
); @@ -772,6 +798,7 @@ export function ValidatorBlockDesktop({ symbol: string; isRewardPending?: boolean; }) { + const { t } = useTranslate(); const router = useRouter(); const isWarningShown = validatorInfo.jailed || validatorInfo.status === 'BOND_STATUS_UNBONDED'; @@ -797,15 +824,18 @@ export function ValidatorBlockDesktop({
- Validator + {t('validator', 'Validator', { ns: 'staking' })}
{isWarningShown && (
- While the validator is inactive, you will not be able to receive a - reward. + {t( + 'validator-inactive-warning', + 'While the validator is inactive, you will not be able to receive a reward.', + { ns: 'staking' }, + )}
)} @@ -813,7 +843,7 @@ export function ValidatorBlockDesktop({
- My delegation + {t('my-delegation', 'My delegation', { ns: 'staking' })} {formatNumber(delegation)} {symbol.toLocaleUpperCase()} @@ -834,7 +864,7 @@ export function ValidatorBlockDesktop({ }} data-attr="delegate" > - Delegate + {t('delegate', 'Delegate', { ns: 'common' })}
@@ -852,7 +882,7 @@ export function ValidatorBlockDesktop({ }} data-attr="undelegate" > - Undelegate + {t('undelegate', 'Undelegate', { ns: 'common' })}
@@ -871,14 +901,14 @@ export function ValidatorBlockDesktop({ }} data-attr="redelegate" > - Redelegate + {t('redelegate', 'Redelegate', { ns: 'common' })}
- My rewards + {t('my-rewards', 'My rewards', { ns: 'staking' })} {formatNumber(rewards)} {symbol.toLocaleUpperCase()} @@ -888,7 +918,11 @@ export function ValidatorBlockDesktop({ @@ -900,7 +934,7 @@ export function ValidatorBlockDesktop({ className="w-full" data-attr="get-my-rewards" > - Get my rewards + {t('get-my-rewards', 'Get my rewards', { ns: 'staking' })}
diff --git a/libs/staking/src/lib/components/validator-list-desktop.tsx b/libs/staking/src/lib/components/validator-list-desktop.tsx index ad21c0233..26a86465d 100644 --- a/libs/staking/src/lib/components/validator-list-desktop.tsx +++ b/libs/staking/src/lib/components/validator-list-desktop.tsx @@ -4,6 +4,7 @@ import { GetDelegationsResponse, Validator, } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { ValidatorListItemDesktop } from './validator-list-item-desktop'; import { SortDirection, SortState } from '../hooks/use-validator-sort'; @@ -37,6 +38,7 @@ export function ValidatorsListDesktop({ onDesktopSortClick: (key: string) => void; sortState: SortState; }) { + const { t } = useTranslate(); const getValidatorRewards = useCallback( (address: string) => { const rewards = rewardsInfo?.rewards?.find((rewardsItem) => { @@ -82,7 +84,7 @@ export function ValidatorsListDesktop({ onDesktopSortClick('name'); }} > - Name + {t('name', 'Name', { ns: 'common' })} {sortState.key !== 'random' && sortState.key === 'name' && ( )} @@ -90,7 +92,7 @@ export function ValidatorsListDesktop({
- Status + {t('status', 'Status', { ns: 'common' })} {sortState.key !== 'random' && sortState.key === 'status' && ( )} @@ -108,7 +110,7 @@ export function ValidatorsListDesktop({ onDesktopSortClick('fee'); }} > - Fee + {t('fee', 'Fee', { ns: 'common' })} {sortState.key !== 'random' && sortState.key === 'fee' && ( )} @@ -126,7 +128,7 @@ export function ValidatorsListDesktop({ onDesktopSortClick('votingPower'); }} > - Voting power + {t('voting-power', 'Voting power', { ns: 'common' })} {sortState.key !== 'random' && sortState.key === 'votingPower' && ( @@ -145,7 +147,7 @@ export function ValidatorsListDesktop({ onDesktopSortClick('votingPowerPercent'); }} > - Voting power % + {t('voting-power', 'Voting power', { ns: 'common' })} % {sortState.key !== 'random' && sortState.key === 'votingPowerPercent' && ( @@ -164,7 +166,7 @@ export function ValidatorsListDesktop({ onDesktopSortClick('staked'); }} > - My stake + {t('my-stake', 'My stake', { ns: 'staking' })} {sortState.key !== 'random' && sortState.key === 'staked' && ( )} @@ -182,7 +184,7 @@ export function ValidatorsListDesktop({ onDesktopSortClick('reward'); }} > - My rewards + {t('my-rewards', 'My rewards', { ns: 'staking' })} {sortState.key !== 'random' && sortState.key === 'reward' && ( )} diff --git a/libs/staking/src/lib/components/validator-list-item-mobile.tsx b/libs/staking/src/lib/components/validator-list-item-mobile.tsx index ccc9d92f6..2416cefd1 100644 --- a/libs/staking/src/lib/components/validator-list-item-mobile.tsx +++ b/libs/staking/src/lib/components/validator-list-item-mobile.tsx @@ -1,6 +1,7 @@ 'use client'; import { PropsWithChildren, useCallback, useMemo } from 'react'; import type { Validator, DelegationResponse, Reward } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import { formatUnits, parseUnits } from 'viem'; import { formatNumber, Card } from '@haqq/shell-ui-kit/server'; @@ -35,36 +36,47 @@ export function ValidatorListItemMobileComponent({ votingPower, votingPowerPercent, }: ValidatorListItemMobileProps) { + const { t } = useTranslate(); return (
- + {validatorName} - + {status === 'jailed' && ( - Jailed + + {t('jailed-status', 'Jailed', { ns: 'common' })} + )} {status === 'active' && ( - Active + + {t('active-status', 'Active', { ns: 'common' })} + )} {status === 'inactive' && ( - Inactive + + {t('inactive-status', 'Inactive', { ns: 'common' })} + )} - + {fee} - + {votingPower} - + {votingPowerPercent} - + {staked} - + {reward}
diff --git a/libs/staking/src/lib/components/validator-list-mobile.tsx b/libs/staking/src/lib/components/validator-list-mobile.tsx index e625477d5..b745487ac 100644 --- a/libs/staking/src/lib/components/validator-list-mobile.tsx +++ b/libs/staking/src/lib/components/validator-list-mobile.tsx @@ -4,6 +4,7 @@ import { GetDelegationsResponse, Validator, } from '@evmos/provider'; +import { useTranslate } from '@tolgee/react'; import { ValidatorListItemMobile } from './validator-list-item-mobile'; export function ValidatorsListMobile({ @@ -19,6 +20,7 @@ export function ValidatorsListMobile({ onValidatorClick: (validatorAddress: string) => void; totalStaked: number; }) { + const { t } = useTranslate('staking'); const getValidatorRewards = useCallback( (address: string) => { const rewards = rewardsInfo?.rewards?.find((rewardsItem) => { @@ -47,7 +49,7 @@ export function ValidatorsListMobile({ return (
- Nothing found + {t('nothing-found', 'Nothing found')}
); diff --git a/libs/staking/src/lib/components/validator-list.tsx b/libs/staking/src/lib/components/validator-list.tsx index 270ee61a2..40c4d7c99 100644 --- a/libs/staking/src/lib/components/validator-list.tsx +++ b/libs/staking/src/lib/components/validator-list.tsx @@ -1,5 +1,6 @@ 'use client'; import { useCallback, useMemo } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import { useRouter } from 'next/navigation'; import { useMediaQuery } from 'usehooks-ts'; @@ -23,6 +24,7 @@ export function ValidatorList({ isMobileUserAgent: boolean; seedPhrase: string; }) { + const { t } = useTranslate(); const { totalStaked, valsTotal, @@ -109,7 +111,7 @@ export function ValidatorList({
- Validators + {t('validators', 'Validators', { ns: 'staking' })} {status !== 'pending' && (  ({validatorsCounterText}) @@ -121,17 +123,38 @@ export function ValidatorList({
@@ -143,7 +166,7 @@ export function ValidatorList({ disabled={!isWalletConnected} value={isWalletConnected ? isShowMyDelegation : false} > - My delegations + {t('my-delegations', 'My delegations', { ns: 'common' })}
@@ -151,7 +174,7 @@ export function ValidatorList({ onChange={setInactiveValidatorsVisible} value={isInactiveValidatorsVisible} > - Show Inactive + {t('show-inactive', 'Show inactive', { ns: 'staking' })}
@@ -163,13 +186,16 @@ export function ValidatorList({
- Fetching validators list + {t('fetching-validators-message', 'Fetching validators list', { + ns: 'staking', + })}
)} {status === 'error' && ( + // eslint-disable-next-line i18next/no-literal-string

Error: {error?.message ?? 'unknown error'}

)} diff --git a/libs/staking/src/lib/components/validator-select.tsx b/libs/staking/src/lib/components/validator-select.tsx index 8131924e6..9028d5e8b 100644 --- a/libs/staking/src/lib/components/validator-select.tsx +++ b/libs/staking/src/lib/components/validator-select.tsx @@ -1,5 +1,6 @@ 'uce client'; import { useCallback, useMemo } from 'react'; +import { useTranslate } from '@tolgee/react'; import clsx from 'clsx'; import Select, { components as validatorSelectComponents, @@ -33,6 +34,7 @@ export function ValidatorSelect({ validators: Array; onChange: (validatorAddress?: string) => void; }) { + const { t } = useTranslate('staking'); const handleFilterOption = useCallback( ({ label, value }: ValidatorSelectOption, inputValue: string) => { const inputLower = inputValue.toLowerCase(); @@ -94,7 +96,7 @@ export function ValidatorSelect({ return (