feat(i18n): replace all hardcoded strings with translation calls

- Add translation keys for dashboard, send, receive, history, swap,
  pools, staking, lpStaking, fees, tokens, errors, validation, time,
  and context sections to types.ts and all 6 language files
- Replace hardcoded Kurdish/Turkish strings in all wallet components
  with useTranslation() hook t() calls
- Replace hardcoded strings in non-React files (crypto, utils,
  error-tracking, wallet-storage, contexts) with standalone translate()
- Fix Turkish strings incorrectly used in Kurdish codebase
This commit is contained in:
2026-02-14 18:16:08 +03:00
parent 71f142b9f4
commit c4282f5870
23 changed files with 2294 additions and 253 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pezkuwi-telegram-miniapp",
"version": "1.0.189",
"version": "1.0.190",
"type": "module",
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
"author": "Pezkuwichain Team",
+32 -24
View File
@@ -16,6 +16,7 @@ import {
} from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
type TargetChain = 'asset-hub' | 'people';
@@ -31,14 +32,14 @@ const TARGET_CHAINS: ChainInfo[] = [
{
id: 'asset-hub',
name: 'Asset Hub',
description: 'Ji bo PEZ veguheztin',
description: 'fees.forTransfers',
teyrchainId: 1000,
color: 'blue',
},
{
id: 'people',
name: 'People Chain',
description: 'Ji bo nasname',
description: 'fees.forIdentity',
teyrchainId: 1004,
color: 'purple',
},
@@ -52,6 +53,7 @@ interface Props {
export function FundFeesModal({ isOpen, onClose }: Props) {
const { api, assetHubApi, peopleApi, address, keypair } = useWallet();
const { hapticImpact, showAlert } = useTelegram();
const { t } = useTranslation();
const [targetChain, setTargetChain] = useState<TargetChain>('asset-hub');
const [toRelay, setToRelay] = useState(false); // false = Relay→Teyrchain, true = Teyrchain→Relay
@@ -142,7 +144,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
const handleTeleport = async () => {
if (!address || !keypair) {
showAlert('Cizdan girêdayî nîne');
showAlert(t('fees.walletNotConnected'));
return;
}
@@ -151,18 +153,18 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
const sourceApi: any = toRelay ? (targetChain === 'asset-hub' ? assetHubApi : peopleApi) : api;
if (!sourceApi) {
showAlert('API girêdayî nîne');
showAlert(t('fees.apiNotConnected'));
return;
}
if (!amount || parseFloat(amount) <= 0) {
showAlert('Mîqdarek rast binivîse');
showAlert(t('fees.enterValidAmount'));
return;
}
const sourceBalance = getSourceBalance();
if (sourceBalance === '--') {
showAlert('Zincîr girêdayî nîne');
showAlert(t('fees.chainNotConnected'));
return;
}
@@ -170,7 +172,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
const currentBalance = parseFloat(sourceBalance);
if (sendAmount > currentBalance) {
showAlert('Bakiye têrê nake');
showAlert(t('fees.insufficientBalance'));
return;
}
@@ -282,7 +284,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
const xcmPallet = toRelay ? (sourceApi.tx as any).pezkuwiXcm : sourceApi.tx.xcmPallet;
if (!xcmPallet?.limitedTeleportAssets) {
throw new Error('XCM pallet nehate dîtin');
throw new Error(t('fees.xcmPalletNotFound'));
}
const tx = xcmPallet.limitedTeleportAssets(
@@ -306,7 +308,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
}) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMessage = 'Teleport neserketî';
let errorMessage = t('fees.teleportFailed');
if (dispatchError.isModule) {
const decoded = sourceApi.registry.findMetaError(dispatchError.asModule);
@@ -337,7 +339,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
setTxStatus('error');
setIsTransferring(false);
hapticImpact('heavy');
showAlert(error instanceof Error ? error.message : 'Çewtiyekî çêbû');
showAlert(error instanceof Error ? error.message : t('fees.errorOccurred'));
}
};
@@ -361,8 +363,8 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
<Fuel className="w-5 h-5 text-yellow-400" />
</div>
<div>
<h2 className="text-lg font-semibold">Fee Zêde Bike</h2>
<p className="text-xs text-muted-foreground">HEZ teleport</p>
<h2 className="text-lg font-semibold">{t('fees.title')}</h2>
<p className="text-xs text-muted-foreground">{t('fees.subtitle')}</p>
</div>
</div>
<button
@@ -377,27 +379,29 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
{txStatus === 'success' ? (
<div className="py-8 text-center">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h3 className="text-xl font-semibold mb-2">Serketî!</h3>
<h3 className="text-xl font-semibold mb-2">{t('fees.success')}</h3>
<p className="text-muted-foreground">
{amount} HEZ bo {getDestName()} hate şandin
{t('fees.sentTo', { amount, chain: getDestName() })}
</p>
</div>
) : txStatus === 'error' ? (
<div className="py-8 text-center">
<AlertCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
<h3 className="text-xl font-semibold mb-2">Neserketî</h3>
<h3 className="text-xl font-semibold mb-2">{t('fees.failed')}</h3>
<button
onClick={() => setTxStatus('idle')}
className="mt-4 px-6 py-2 bg-muted rounded-lg"
>
Dîsa Biceribîne
{t('fees.tryAgain')}
</button>
</div>
) : (
<div className="space-y-4">
{/* Target Chain Selection */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Zincîra Armanc</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('fees.targetChain')}
</label>
<div className="flex gap-2">
{TARGET_CHAINS.map((chain) => (
<button
@@ -420,7 +424,9 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
}`}
/>
<div className="text-sm font-medium">{chain.name}</div>
<div className="text-xs text-muted-foreground">{chain.description}</div>
<div className="text-xs text-muted-foreground">
{t(chain.description as any)}
</div>
</button>
))}
</div>
@@ -499,13 +505,15 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
targetChain === 'asset-hub' ? 'text-blue-400' : 'text-purple-400'
}`}
>
{selectedChain.description} kêmî 0.1 HEZ pêşniyarkirin.
{t('fees.minRecommended', { description: t(selectedChain.description as any) })}
</p>
</div>
{/* Amount Input */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Mîqdar (HEZ)</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('fees.amountHez')}
</label>
<input
type="number"
step="0.0001"
@@ -537,7 +545,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
{/* Status Messages */}
{txStatus === 'signing' && (
<div className="p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<p className="text-yellow-400 text-sm">Danûstandinê îmze bikin...</p>
<p className="text-yellow-400 text-sm">{t('fees.signing')}</p>
</div>
)}
@@ -545,7 +553,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
<div className="p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
<p className="text-blue-400 text-sm flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
XCM Teleport çêkirin...
{t('fees.xcmTeleportPending')}
</p>
</div>
)}
@@ -559,12 +567,12 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
{isTransferring ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
{txStatus === 'signing' ? 'Tê îmzekirin...' : 'Tê çêkirin...'}
{txStatus === 'signing' ? t('fees.signingButton') : t('fees.processing')}
</>
) : (
<>
<Fuel className="w-5 h-5" />
Bo {getDestName()} Bişîne
{t('fees.sendTo', { chain: getDestName() })}
</>
)}
</button>
+55 -48
View File
@@ -7,6 +7,7 @@ import { useState, useEffect, useCallback } from 'react';
import { X, Lock, Unlock, Users, AlertCircle, Loader2, Shield, TrendingUp } from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
import { cn } from '@/lib/utils';
interface StakingInfo {
@@ -35,6 +36,7 @@ const UNITS = 1_000_000_000_000; // 10^12
export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
const { api, keypair, address, balance } = useWallet();
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
const { t } = useTranslation();
const [stakingInfo, setStakingInfo] = useState<StakingInfo | null>(null);
const [validators, setValidators] = useState<ValidatorInfo[]>([]);
@@ -55,7 +57,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stakingPallet = api.query.staking as any;
if (!stakingPallet) {
setError('Staking palleti bulunamadı');
setError(t('staking.palletNotFound'));
setIsLoading(false);
return;
}
@@ -114,7 +116,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
setValidators(validatorList);
} catch (err) {
console.error('Error fetching staking info:', err);
setError('Staking bilgileri alınamadı');
setError(t('staking.fetchError'));
} finally {
setIsLoading(false);
}
@@ -165,7 +167,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
}) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Bond neserketî';
let errorMsg = t('staking.bondFailed');
if (dispatchError.isModule) {
const decoded = api.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}`;
@@ -180,13 +182,13 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
});
hapticNotification('success');
showAlert(`${bondAmount} HEZ stake kirin serketî!`);
showAlert(t('staking.bondSuccess', { amount: bondAmount }));
setBondAmount('');
fetchStakingInfo();
setActiveTab('status');
} catch (err) {
console.error('Bond error:', err);
setError(err instanceof Error ? err.message : 'Bond neserketî');
setError(err instanceof Error ? err.message : t('staking.bondFailed'));
hapticNotification('error');
} finally {
setIsProcessing(false);
@@ -216,7 +218,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
}) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Nominate neserketî';
let errorMsg = t('staking.nominateFailed');
if (dispatchError.isModule) {
const decoded = api.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}`;
@@ -231,12 +233,12 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
});
hapticNotification('success');
showAlert(`${selectedValidators.length} validator nominate kirin serketî!`);
showAlert(t('staking.nominateSuccess', { count: selectedValidators.length }));
fetchStakingInfo();
setActiveTab('status');
} catch (err) {
console.error('Nominate error:', err);
setError(err instanceof Error ? err.message : 'Nominate neserketî');
setError(err instanceof Error ? err.message : t('staking.nominateFailed'));
hapticNotification('error');
} finally {
setIsProcessing(false);
@@ -268,7 +270,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
}) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Unbond neserketî';
let errorMsg = t('staking.unbondFailed');
if (dispatchError.isModule) {
const decoded = api.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}`;
@@ -283,13 +285,13 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
});
hapticNotification('success');
showAlert(`${unbondAmount} HEZ unbond kirin serketî! (28 roj li bendê)`);
showAlert(t('staking.unbondSuccess', { amount: unbondAmount }));
setUnbondAmount('');
fetchStakingInfo();
setActiveTab('status');
} catch (err) {
console.error('Unbond error:', err);
setError(err instanceof Error ? err.message : 'Unbond neserketî');
setError(err instanceof Error ? err.message : t('staking.unbondFailed'));
hapticNotification('error');
} finally {
setIsProcessing(false);
@@ -330,10 +332,10 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
{/* Tabs */}
<div className="flex gap-1 bg-secondary/50 rounded-lg p-1 mb-4">
{[
{ id: 'status' as const, label: 'Durum', icon: TrendingUp },
{ id: 'bond' as const, label: 'Bond', icon: Lock },
{ id: 'nominate' as const, label: 'Nominate', icon: Users },
{ id: 'unbond' as const, label: 'Unbond', icon: Unlock },
{ id: 'status' as const, label: t('staking.statusTab'), icon: TrendingUp },
{ id: 'bond' as const, label: t('staking.bondTab'), icon: Lock },
{ id: 'nominate' as const, label: t('staking.nominateTab'), icon: Users },
{ id: 'unbond' as const, label: t('staking.unbondTab'), icon: Unlock },
].map(({ id, label, icon: Icon }) => (
<button
key={id}
@@ -368,7 +370,9 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
{stakingInfo ? (
<>
<div className="bg-gradient-to-r from-green-500/20 to-emerald-500/20 border border-green-500/30 rounded-xl p-4">
<div className="text-sm text-muted-foreground mb-1">Aktîf Stake</div>
<div className="text-sm text-muted-foreground mb-1">
{t('staking.activeStake')}
</div>
<div className="text-2xl font-bold text-green-400">
{formatHEZ(stakingInfo.active)} HEZ
</div>
@@ -376,20 +380,24 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
<div className="bg-secondary/50 rounded-xl p-4 space-y-3">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Giştî Bonded</span>
<span className="text-muted-foreground">{t('staking.totalBonded')}</span>
<span>{formatHEZ(stakingInfo.totalBonded)} HEZ</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Nominations</span>
<span className="text-muted-foreground">{t('staking.nominations')}</span>
<span>{stakingInfo.nominations.length} validator</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Xelat Armanc</span>
<span className="text-muted-foreground">
{t('staking.rewardDestination')}
</span>
<span>{stakingInfo.rewardDestination}</span>
</div>
{stakingInfo.unlocking.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Unbonding</span>
<span className="text-muted-foreground">
{t('staking.unbondingChunks')}
</span>
<span className="text-yellow-400">
{stakingInfo.unlocking.length} chunk
</span>
@@ -400,7 +408,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
{stakingInfo.nominations.length > 0 && (
<div className="bg-secondary/50 rounded-xl p-4">
<div className="text-sm text-muted-foreground mb-2">
Nominated Validators
{t('staking.nominatedValidators')}
</div>
<div className="space-y-1 max-h-32 overflow-y-auto">
{stakingInfo.nominations.map((addr, i) => (
@@ -413,24 +421,21 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
)}
<div className="p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
<p className="text-blue-400 text-xs">
💡 HEZ stake kirin Trust Score zêde dike. Herî kêm 1 meh stake bikî ji bo
bonusê.
</p>
<p className="text-blue-400 text-xs">{t('staking.stakingTip')}</p>
</div>
</>
) : (
<div className="text-center py-8">
<Shield className="w-16 h-16 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-medium mb-2">Hîn Stake Nekiriye</h3>
<h3 className="text-lg font-medium mb-2">{t('staking.notStakedYet')}</h3>
<p className="text-muted-foreground text-sm mb-4">
HEZ stake bike ji bo Trust Score qezenckirin
{t('staking.stakeForTrustScore')}
</p>
<button
onClick={() => setActiveTab('bond')}
className="px-6 py-2 bg-green-600 text-white rounded-lg text-sm"
>
Dest Bike
{t('staking.startStaking')}
</button>
</div>
)}
@@ -442,19 +447,23 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
<div className="space-y-4">
<div className="bg-secondary/50 rounded-xl p-4">
<div className="flex justify-between text-sm mb-2">
<span className="text-muted-foreground">Bakiyê Te</span>
<span className="text-muted-foreground">{t('staking.yourBalance')}</span>
<span>{balance || '0'} HEZ</span>
</div>
{stakingInfo && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Niha Staked</span>
<span className="text-muted-foreground">
{t('staking.currentlyStaked')}
</span>
<span className="text-green-400">{formatHEZ(stakingInfo.active)} HEZ</span>
</div>
)}
</div>
<div>
<label className="text-sm text-muted-foreground mb-2 block">Mîqdar (HEZ)</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('staking.amountHez')}
</label>
<div className="relative">
<input
type="number"
@@ -473,9 +482,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
</div>
<div className="p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<p className="text-yellow-400 text-xs">
Stake kirinê paşê 28 roj li bendê ye ji bo vekişandinê.
</p>
<p className="text-yellow-400 text-xs">{t('staking.bondWarning')}</p>
</div>
<button
@@ -488,7 +495,11 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
) : (
<Lock className="w-5 h-5" />
)}
{isProcessing ? 'Tê bond kirin...' : stakingInfo ? 'Zêde Bike' : 'Bond Bike'}
{isProcessing
? t('staking.bonding')
: stakingInfo
? t('staking.bondExtra')
: t('staking.bondButton')}
</button>
</div>
)}
@@ -499,14 +510,12 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
{!stakingInfo ? (
<div className="text-center py-8">
<AlertCircle className="w-12 h-12 text-yellow-400 mx-auto mb-4" />
<p className="text-muted-foreground">
Pêşî HEZ bond bike, paşê nominate bike
</p>
<p className="text-muted-foreground">{t('staking.bondFirst')}</p>
</div>
) : (
<>
<div className="text-sm text-muted-foreground mb-2">
Validator hilbijêre (max 16): {selectedValidators.length}/16
{t('staking.selectValidators', { count: selectedValidators.length })}
</div>
<div className="space-y-2 max-h-64 overflow-y-auto">
@@ -525,7 +534,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
{v.address.slice(0, 16)}...{v.address.slice(-8)}
</div>
<div className="text-xs text-muted-foreground mt-1">
Komîsyon: {v.commission.toFixed(2)}%
{t('staking.commission')} {v.commission.toFixed(2)}%
</div>
</button>
))}
@@ -541,7 +550,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
) : (
<Users className="w-5 h-5" />
)}
{isProcessing ? 'Tê nominate kirin...' : 'Nominate Bike'}
{isProcessing ? t('staking.nominating') : t('staking.nominateButton')}
</button>
</>
)}
@@ -554,13 +563,13 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
{!stakingInfo ? (
<div className="text-center py-8">
<AlertCircle className="w-12 h-12 text-yellow-400 mx-auto mb-4" />
<p className="text-muted-foreground">Tu hîn stake nekiriye</p>
<p className="text-muted-foreground">{t('staking.notStakedUnbond')}</p>
</div>
) : (
<>
<div className="bg-secondary/50 rounded-xl p-4">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Aktîf Stake</span>
<span className="text-muted-foreground">{t('staking.activeStake')}</span>
<span className="text-green-400">
{formatHEZ(stakingInfo.active)} HEZ
</span>
@@ -569,7 +578,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
<div>
<label className="text-sm text-muted-foreground mb-2 block">
Mîqdar (HEZ)
{t('staking.amountHez')}
</label>
<div className="relative">
<input
@@ -589,9 +598,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
</div>
<div className="p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
<p className="text-orange-400 text-xs">
Unbond kirin 28 roj digire. Paşê dikare vekişîne.
</p>
<p className="text-orange-400 text-xs">{t('staking.unbondWarning')}</p>
</div>
<button
@@ -604,7 +611,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
) : (
<Unlock className="w-5 h-5" />
)}
{isProcessing ? 'Tê unbond kirin...' : 'Unbond Bike'}
{isProcessing ? t('staking.unbondProcessing') : t('staking.unbondButton')}
</button>
</>
)}
+34 -28
View File
@@ -7,6 +7,7 @@ import { useState, useEffect } from 'react';
import { X, Lock, Unlock, Gift, AlertCircle, Loader2 } from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
import { cn } from '@/lib/utils';
interface StakingPool {
@@ -56,6 +57,7 @@ function formatAssetLocation(assetId: number): object {
export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
const { assetHubApi, keypair, address } = useWallet();
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
const { t } = useTranslation();
const [pools, setPools] = useState<StakingPool[]>([]);
const [isLoading, setIsLoading] = useState(true);
@@ -76,7 +78,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
const poolEntries = await (assetHubApi.query.assetRewards as any)?.pools?.entries();
if (!poolEntries) {
setError('Staking palleti amade nîne');
setError(t('lpStaking.palletNotReady'));
setIsLoading(false);
return;
}
@@ -170,7 +172,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
}
} catch (err) {
console.error('Error fetching staking pools:', err);
setError('Staking pools bar nekirin');
setError(t('lpStaking.poolsNotLoaded'));
} finally {
setIsLoading(false);
}
@@ -212,7 +214,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
}) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Stake neserketî';
let errorMsg = t('lpStaking.stakeFailed');
if (dispatchError.isModule) {
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}`;
@@ -227,14 +229,14 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
});
hapticNotification('success');
showAlert('Stake serket!');
showAlert(t('lpStaking.stakeSuccess'));
setStakeAmount('');
setTimeout(() => {
onClose();
}, 1500);
} catch (err) {
setError(err instanceof Error ? err.message : 'Stake neserketî');
setError(err instanceof Error ? err.message : t('lpStaking.stakeFailed'));
hapticNotification('error');
} finally {
setIsProcessing(false);
@@ -266,7 +268,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
}) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Unstake neserketî';
let errorMsg = t('lpStaking.unstakeFailed');
if (dispatchError.isModule) {
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}`;
@@ -281,14 +283,14 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
});
hapticNotification('success');
showAlert('Unstake serket!');
showAlert(t('lpStaking.unstakeSuccess'));
setUnstakeAmount('');
setTimeout(() => {
onClose();
}, 1500);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unstake neserketî');
setError(err instanceof Error ? err.message : t('lpStaking.unstakeFailed'));
hapticNotification('error');
} finally {
setIsProcessing(false);
@@ -318,7 +320,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
}) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Xelat stendin neserketî';
let errorMsg = t('lpStaking.claimFailed');
if (dispatchError.isModule) {
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}`;
@@ -333,13 +335,13 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
});
hapticNotification('success');
showAlert('Xelat hat stendin!');
showAlert(t('lpStaking.claimSuccess'));
setTimeout(() => {
onClose();
}, 1500);
} catch (err) {
setError(err instanceof Error ? err.message : 'Xelat stendin neserketî');
setError(err instanceof Error ? err.message : t('lpStaking.claimFailed'));
hapticNotification('error');
} finally {
setIsProcessing(false);
@@ -370,13 +372,15 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
) : pools.length === 0 ? (
<div className="text-center py-12">
<AlertCircle className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground">Hêj staking pool tune ne</p>
<p className="text-muted-foreground">{t('lpStaking.noPoolsYet')}</p>
</div>
) : (
<>
{/* Pool Selector */}
<div className="mb-4">
<label className="text-sm text-muted-foreground mb-2 block">Pool Hilbijêre</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('lpStaking.selectPool')}
</label>
<div className="grid grid-cols-3 gap-2">
{pools.map((pool) => (
<button
@@ -403,21 +407,21 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
<div className="bg-secondary/50 rounded-xl p-4 mb-4 border border-border">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<div className="text-muted-foreground">Giştî Staked</div>
<div className="text-muted-foreground">{t('lpStaking.totalStaked')}</div>
<div className="font-medium">{formatAmount(currentPool.totalStaked)}</div>
</div>
<div>
<div className="text-muted-foreground">Te Stake Kiriye</div>
<div className="text-muted-foreground">{t('lpStaking.youStaked')}</div>
<div className="font-medium text-green-400">
{formatAmount(currentPool.userStaked)}
</div>
</div>
<div>
<div className="text-muted-foreground">LP Bakiye</div>
<div className="text-muted-foreground">{t('lpStaking.lpBalance')}</div>
<div className="font-medium">{formatAmount(currentPool.lpBalance)}</div>
</div>
<div>
<div className="text-muted-foreground">Xelat</div>
<div className="text-muted-foreground">{t('lpStaking.reward')}</div>
<div className="font-medium text-yellow-400">
{formatAmount(currentPool.pendingRewards)} PEZ
</div>
@@ -429,9 +433,9 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
{/* Tabs */}
<div className="flex gap-1 bg-secondary/50 rounded-lg p-1 mb-4">
{[
{ id: 'stake' as const, label: 'Stake', icon: Lock },
{ id: 'unstake' as const, label: 'Unstake', icon: Unlock },
{ id: 'rewards' as const, label: 'Xelat', icon: Gift },
{ id: 'stake' as const, label: t('lpStaking.stakeTab'), icon: Lock },
{ id: 'unstake' as const, label: t('lpStaking.unstakeTab'), icon: Unlock },
{ id: 'rewards' as const, label: t('lpStaking.rewardTab'), icon: Gift },
].map(({ id, label, icon: Icon }) => (
<button
key={id}
@@ -464,7 +468,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
<div className="space-y-4">
<div>
<label className="text-sm text-muted-foreground mb-2 block">
Mîqdar ({currentPool.stakedAsset})
{t('lpStaking.amount', { asset: currentPool.stakedAsset })}
</label>
<div className="relative">
<input
@@ -482,7 +486,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
</button>
</div>
<div className="text-xs text-muted-foreground mt-1">
Bakiye: {formatAmount(currentPool.lpBalance)}
{t('lpStaking.balanceLabel')} {formatAmount(currentPool.lpBalance)}
</div>
</div>
<button
@@ -495,7 +499,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
) : (
<Lock className="w-5 h-5" />
)}
{isProcessing ? 'Tê stake kirin...' : 'Stake Bike'}
{isProcessing ? t('lpStaking.staking') : t('lpStaking.stakeButton')}
</button>
</div>
)}
@@ -505,7 +509,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
<div className="space-y-4">
<div>
<label className="text-sm text-muted-foreground mb-2 block">
Mîqdar ({currentPool.stakedAsset})
{t('lpStaking.amount', { asset: currentPool.stakedAsset })}
</label>
<div className="relative">
<input
@@ -523,7 +527,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
</button>
</div>
<div className="text-xs text-muted-foreground mt-1">
Staked: {formatAmount(currentPool.userStaked)}
{t('lpStaking.stakedLabel')} {formatAmount(currentPool.userStaked)}
</div>
</div>
<button
@@ -536,7 +540,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
) : (
<Unlock className="w-5 h-5" />
)}
{isProcessing ? 'Tê unstake kirin...' : 'Unstake Bike'}
{isProcessing ? t('lpStaking.unstaking') : t('lpStaking.unstakeButton')}
</button>
</div>
)}
@@ -549,7 +553,9 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
<div className="text-2xl font-bold text-yellow-400 mb-1">
{formatAmount(currentPool.pendingRewards)} PEZ
</div>
<div className="text-sm text-muted-foreground">Xelatên li bendê</div>
<div className="text-sm text-muted-foreground">
{t('lpStaking.pendingRewards')}
</div>
</div>
<button
onClick={handleClaimRewards}
@@ -561,7 +567,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
) : (
<Gift className="w-5 h-5" />
)}
{isProcessing ? 'Tê stendin...' : 'Xelatan Bistîne'}
{isProcessing ? t('lpStaking.claiming') : t('lpStaking.claimButton')}
</button>
</div>
)}
+42 -31
View File
@@ -8,6 +8,7 @@ import { X, Droplets, Plus, Minus, AlertCircle, Check } from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { KurdistanSun } from '@/components/KurdistanSun';
import { useTranslation } from '@/i18n';
interface PoolsModalProps {
isOpen: boolean;
@@ -52,6 +53,7 @@ const formatAssetLocation = (id: number) => {
export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
const { assetHubApi, keypair } = useWallet();
const { hapticImpact, hapticNotification } = useTelegram();
const { t } = useTranslation();
const [pools, setPools] = useState<Pool[]>([]);
const [isLoading, setIsLoading] = useState(false);
@@ -267,7 +269,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
} catch (err) {
console.error('Failed to fetch pools:', err);
if (!isCancelled) {
setError('Bağlantı hatası - tekrar deneyin');
setError(t('pools.connectionError'));
}
} finally {
if (!isCancelled) {
@@ -331,7 +333,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
({ status, dispatchError }: { status: any; dispatchError: any }) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Zêdekirin neserketî';
let errorMsg = t('pools.addFailed');
if (dispatchError.isModule) {
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
@@ -349,7 +351,12 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
});
setSuccessMessage(
`${amount0} ${selectedPool.asset0Symbol} + ${amount1} ${selectedPool.asset1Symbol} hate zêdekirin`
t('pools.addedLiquidity', {
amount0,
token0: selectedPool.asset0Symbol,
amount1,
token1: selectedPool.asset1Symbol,
})
);
setSuccess(true);
hapticNotification('success');
@@ -363,7 +370,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
}, 2000);
} catch (err) {
console.error('Add liquidity failed:', err);
setError(err instanceof Error ? err.message : 'Zêdekirin neserketî');
setError(err instanceof Error ? err.message : t('pools.addFailed'));
hapticNotification('error');
} finally {
setIsSubmitting(false);
@@ -376,7 +383,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
const lpAmount = parseFloat(lpAmountToRemove);
if (lpAmount <= 0 || lpAmount > (selectedPool.userLpBalance || 0)) {
setError('Mîqdara LP ne derbasdar e');
setError(t('pools.invalidLpAmount'));
hapticNotification('error');
return;
}
@@ -420,7 +427,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
({ status, dispatchError }: { status: any; dispatchError: any }) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Derxistin neserketî';
let errorMsg = t('pools.removeFailed');
if (dispatchError.isModule) {
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
@@ -437,7 +444,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
).catch(reject);
});
setSuccessMessage(`${lpAmountToRemove} LP token hate vegerandin`);
setSuccessMessage(t('pools.removedLiquidity', { amount: lpAmountToRemove }));
setSuccess(true);
hapticNotification('success');
@@ -449,7 +456,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
}, 2000);
} catch (err) {
console.error('Remove liquidity failed:', err);
setError(err instanceof Error ? err.message : 'Derxistin neserketî');
setError(err instanceof Error ? err.message : t('pools.removeFailed'));
hapticNotification('error');
} finally {
setIsSubmitting(false);
@@ -466,7 +473,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
<div className="w-16 h-16 mx-auto bg-green-500/20 rounded-full flex items-center justify-center">
<Check className="w-8 h-8 text-green-500" />
</div>
<h2 className="text-xl font-semibold">Serketî!</h2>
<h2 className="text-xl font-semibold">{t('pools.success')}</h2>
<p className="text-muted-foreground">{successMessage}</p>
</div>
</div>
@@ -489,9 +496,9 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
}}
className="text-muted-foreground"
>
Paş
{t('pools.back')}
</button>
<h2 className="text-lg font-semibold">Liquidity Zêde Bike</h2>
<h2 className="text-lg font-semibold">{t('pools.addLiquidity')}</h2>
<div className="w-10" />
</div>
@@ -515,7 +522,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">{selectedPool.asset0Symbol} Mîqdar</span>
<span className="text-muted-foreground">
Bakiye: {balances[selectedPool.asset0Symbol]}
{t('swap.balanceLabel')} {balances[selectedPool.asset0Symbol]}
</span>
</div>
<div className="flex gap-2">
@@ -543,10 +550,10 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">
{selectedPool.asset1Symbol} Mîqdar (otomatîk)
{t('pools.amountAuto', { token: selectedPool.asset1Symbol })}
</span>
<span className="text-muted-foreground">
Bakiye: {balances[selectedPool.asset1Symbol]}
{t('swap.balanceLabel')} {balances[selectedPool.asset1Symbol]}
</span>
</div>
<input
@@ -570,7 +577,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
{isSubmitting ? (
<div className="flex flex-col items-center justify-center py-4 space-y-3">
<KurdistanSun size={80} />
<p className="text-sm text-muted-foreground animate-pulse"> zêdekirin...</p>
<p className="text-sm text-muted-foreground animate-pulse">{t('pools.adding')}</p>
</div>
) : (
<button
@@ -579,7 +586,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
className="w-full py-4 bg-gradient-to-r from-green-600 to-blue-600 text-white font-semibold rounded-xl disabled:opacity-50 flex items-center justify-center gap-2"
>
<Droplets className="w-5 h-5" />
Liquidity Zêde Bike
{t('pools.addLiquidity')}
</button>
)}
</div>
@@ -603,9 +610,9 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
}}
className="text-muted-foreground"
>
Paş
{t('pools.back')}
</button>
<h2 className="text-lg font-semibold">Liquidity Derxe</h2>
<h2 className="text-lg font-semibold">{t('pools.removeLiquidity')}</h2>
<div className="w-10" />
</div>
@@ -617,7 +624,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
</span>
</div>
<p className="text-center text-sm text-muted-foreground mt-1">
LP Bakiye: {selectedPool.userLpBalance?.toFixed(4) || '0'} LP
{t('pools.lpBalance')} {selectedPool.userLpBalance?.toFixed(4) || '0'} LP
</p>
</div>
@@ -626,7 +633,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
{/* LP Amount */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">LP Token Mîqdar</span>
<span className="text-muted-foreground">{t('pools.lpTokenAmount')}</span>
<span className="text-muted-foreground">
Max: {selectedPool.userLpBalance?.toFixed(4) || '0'}
</span>
@@ -651,7 +658,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
{/* Estimated Returns */}
{lpAmountToRemove && parseFloat(lpAmountToRemove) > 0 && (
<div className="bg-muted/50 rounded-xl p-3 space-y-2 text-sm">
<p className="text-muted-foreground">Texmînî vegerandin:</p>
<p className="text-muted-foreground">{t('pools.estimatedReturn')}</p>
<div className="flex justify-between">
<span>{selectedPool.asset0Symbol}</span>
<span className="font-mono">
@@ -691,7 +698,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
{isSubmitting ? (
<div className="flex flex-col items-center justify-center py-4 space-y-3">
<KurdistanSun size={80} />
<p className="text-sm text-muted-foreground animate-pulse"> derxistin...</p>
<p className="text-sm text-muted-foreground animate-pulse">{t('pools.removing')}</p>
</div>
) : (
<button
@@ -700,7 +707,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
className="w-full py-4 bg-gradient-to-r from-red-600 to-orange-600 text-white font-semibold rounded-xl disabled:opacity-50 flex items-center justify-center gap-2"
>
<Minus className="w-5 h-5" />
Liquidity Derxe
{t('pools.removeLiquidity')}
</button>
)}
</div>
@@ -715,7 +722,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
<div className="w-full max-w-md bg-card rounded-2xl shadow-xl border border-border overflow-hidden max-h-[90vh] overflow-y-auto">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-border">
<h2 className="text-lg font-semibold">Liquidity Pools</h2>
<h2 className="text-lg font-semibold">{t('pools.title')}</h2>
<button onClick={onClose} className="p-2 rounded-full hover:bg-muted">
<X className="w-5 h-5 text-muted-foreground" />
</button>
@@ -726,12 +733,12 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
{isLoading ? (
<div className="flex flex-col items-center justify-center py-8">
<KurdistanSun size={80} />
<p className="text-muted-foreground mt-3 animate-pulse"> barkirin...</p>
<p className="text-muted-foreground mt-3 animate-pulse">{t('pools.loadingPools')}</p>
</div>
) : pools.length === 0 ? (
<div className="text-center py-8">
<Droplets className="w-12 h-12 mx-auto text-muted-foreground mb-2" />
<p className="text-muted-foreground">Pool tune</p>
<p className="text-muted-foreground">{t('pools.noPools')}</p>
</div>
) : (
pools.map((pool) => (
@@ -760,13 +767,17 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
{/* Pool Stats */}
<div className="grid grid-cols-2 gap-2 text-sm mb-3">
<div>
<span className="text-muted-foreground">Rezerv {pool.asset0Symbol}</span>
<span className="text-muted-foreground">
{t('pools.reserve')} {pool.asset0Symbol}
</span>
<p className="font-mono">
{pool.reserve0.toLocaleString('en-US', { maximumFractionDigits: 0 })}
</p>
</div>
<div>
<span className="text-muted-foreground">Rezerv {pool.asset1Symbol}</span>
<span className="text-muted-foreground">
{t('pools.reserve')} {pool.asset1Symbol}
</span>
<p className="font-mono">
{pool.reserve1.toLocaleString('en-US', { maximumFractionDigits: 0 })}
</p>
@@ -777,7 +788,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
{pool.userLpBalance && pool.userLpBalance > 0 && (
<div className="bg-green-500/10 border border-green-500/30 rounded-lg p-2 mb-3 text-sm">
<div className="flex justify-between">
<span className="text-green-400">Pozîsyona Te</span>
<span className="text-green-400">{t('pools.yourPosition')}</span>
<span className="text-green-400 font-mono">
{pool.userShare?.toFixed(2)}%
</span>
@@ -799,7 +810,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
className="flex-1 py-2 bg-gradient-to-r from-green-600/20 to-blue-600/20 border border-green-500/30 text-green-400 font-medium rounded-lg flex items-center justify-center gap-1 text-sm"
>
<Plus className="w-4 h-4" />
Zêde Bike
{t('pools.addButton')}
</button>
{pool.userLpBalance && pool.userLpBalance > 0 && (
<button
@@ -811,7 +822,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
className="flex-1 py-2 bg-gradient-to-r from-red-600/20 to-orange-600/20 border border-red-500/30 text-red-400 font-medium rounded-lg flex items-center justify-center gap-1 text-sm"
>
<Minus className="w-4 h-4" />
Derxe
{t('pools.removeButton')}
</button>
)}
</div>
+15 -13
View File
@@ -8,6 +8,7 @@ import { X, ArrowDownUp, RefreshCw, AlertCircle, Check } from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { KurdistanSun } from '@/components/KurdistanSun';
import { useTranslation } from '@/i18n';
interface SwapModalProps {
isOpen: boolean;
@@ -36,6 +37,7 @@ const formatAssetLocation = (id: number) => {
export function SwapModal({ isOpen, onClose }: SwapModalProps) {
const { assetHubApi, keypair } = useWallet();
const { hapticImpact, hapticNotification } = useTelegram();
const { t } = useTranslation();
const [fromToken, setFromToken] = useState(TOKENS[0]); // HEZ
const [toToken, setToToken] = useState(TOKENS[1]); // PEZ
@@ -218,7 +220,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
const swapAmount = parseFloat(fromAmount);
if (swapAmount > fromBalance) {
setError('Bakiye têrê nake');
setError(t('swap.insufficientBalance'));
hapticNotification('error');
return;
}
@@ -262,7 +264,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
({ status, dispatchError }: { status: any; dispatchError: any }) => {
if (status.isFinalized) {
if (dispatchError) {
let errorMsg = 'Swap neserketî';
let errorMsg = t('swap.swapFailed');
if (dispatchError.isModule) {
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
errorMsg = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
@@ -291,7 +293,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
}, 2000);
} catch (err) {
console.error('Swap failed:', err);
setError(err instanceof Error ? err.message : 'Swap neserketî');
setError(err instanceof Error ? err.message : t('swap.swapFailed'));
hapticNotification('error');
} finally {
setIsSwapping(false);
@@ -307,7 +309,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
<div className="w-16 h-16 mx-auto bg-green-500/20 rounded-full flex items-center justify-center">
<Check className="w-8 h-8 text-green-500" />
</div>
<h2 className="text-xl font-semibold">Swap Serketî!</h2>
<h2 className="text-xl font-semibold">{t('swap.swapSuccess')}</h2>
<p className="text-muted-foreground">
{fromAmount} {fromToken.symbol} {toAmount} {toToken.symbol}
</p>
@@ -321,7 +323,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
<div className="w-full max-w-md bg-card rounded-2xl shadow-xl border border-border overflow-hidden max-h-[90vh] overflow-y-auto">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-border">
<h2 className="text-lg font-semibold">Token Swap</h2>
<h2 className="text-lg font-semibold">{t('swap.title')}</h2>
<button onClick={onClose} className="p-2 rounded-full hover:bg-muted">
<X className="w-5 h-5 text-muted-foreground" />
</button>
@@ -332,7 +334,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
{/* From Token */}
<div className="bg-muted/50 rounded-xl p-4 space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Ji (From)</span>
<span className="text-sm text-muted-foreground">{t('swap.fromLabel')}</span>
<select
value={fromToken.symbol}
onChange={(e) => {
@@ -368,7 +370,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
Max
</button>
<span className="text-muted-foreground">
Bakiye: {balances[fromToken.symbol]} {fromToken.symbol}
{t('swap.balanceLabel')} {balances[fromToken.symbol]} {fromToken.symbol}
</span>
</div>
</div>
@@ -386,7 +388,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
{/* To Token */}
<div className="bg-muted/50 rounded-xl p-4 space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Bo (To)</span>
<span className="text-sm text-muted-foreground">{t('swap.toLabel')}</span>
<select
value={toToken.symbol}
onChange={(e) => {
@@ -416,7 +418,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
/>
<div className="flex justify-end text-sm">
<span className="text-muted-foreground">
Bakiye: {balances[toToken.symbol]} {toToken.symbol}
{t('swap.balanceLabel')} {balances[toToken.symbol]} {toToken.symbol}
</span>
</div>
</div>
@@ -424,14 +426,14 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
{/* Exchange Rate */}
<div className="bg-muted/30 rounded-xl p-3 space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Rêjeya Guherandinê</span>
<span className="text-muted-foreground">{t('swap.exchangeRate')}</span>
<span className="flex items-center gap-2">
{isLoadingRate ? (
<RefreshCw className="w-4 h-4 animate-spin" />
) : exchangeRate ? (
`1 ${fromToken.symbol} = ${exchangeRate.toFixed(4)} ${toToken.symbol}`
) : (
<span className="text-yellow-500">Pool tune</span>
<span className="text-yellow-500">{t('swap.noPool')}</span>
)}
<button onClick={fetchExchangeRate} className="p-1 hover:bg-muted rounded">
<RefreshCw className="w-3 h-3" />
@@ -457,7 +459,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
{isSwapping ? (
<div className="flex flex-col items-center justify-center py-4 space-y-3">
<KurdistanSun size={80} />
<p className="text-sm text-muted-foreground animate-pulse"> guhertin...</p>
<p className="text-sm text-muted-foreground animate-pulse">{t('swap.swapping')}</p>
</div>
) : (
<button
@@ -465,7 +467,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
disabled={!fromAmount || !exchangeRate || parseFloat(fromAmount) <= 0}
className="w-full py-4 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-semibold rounded-xl disabled:opacity-50 flex items-center justify-center gap-2"
>
{!exchangeRate ? 'Pool Tune' : 'Swap Bike'}
{!exchangeRate ? t('swap.noPoolButton') : t('swap.swapButton')}
</button>
)}
</div>
+19 -11
View File
@@ -23,6 +23,7 @@ import {
} from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
import {
subscribeToConnection,
getLastError,
@@ -189,6 +190,7 @@ interface Props {
export function TokensCard({ onSendToken }: Props) {
const { address, balance: hezBalance } = useWallet();
const { hapticImpact } = useTelegram();
const { t } = useTranslation();
const [rpcConnected, setRpcConnected] = useState(false);
const [endpointName, setEndpointName] = useState<string | null>(null);
@@ -615,7 +617,7 @@ export function TokensCard({ onSendToken }: Props) {
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Token bigere..."
placeholder={t('tokens.searchPlaceholder')}
className="w-full pl-9 pr-4 py-2 bg-background rounded-lg text-sm"
/>
</div>
@@ -629,7 +631,7 @@ export function TokensCard({ onSendToken }: Props) {
className="w-full py-2 border border-dashed border-border rounded-lg text-sm text-muted-foreground hover:text-white hover:border-cyan-500/50 flex items-center justify-center gap-2"
>
<Plus className="w-4 h-4" />
Token Zêde Bike
{t('tokens.addToken')}
</button>
)}
@@ -639,7 +641,7 @@ export function TokensCard({ onSendToken }: Props) {
type="number"
value={newAssetId}
onChange={(e) => setNewAssetId(e.target.value)}
placeholder="Asset ID binivîse (mînak: 3)"
placeholder={t('tokens.assetIdPlaceholder')}
className="w-full px-3 py-2 bg-muted rounded-lg text-sm"
min="0"
/>
@@ -648,14 +650,14 @@ export function TokensCard({ onSendToken }: Props) {
onClick={() => setShowAddToken(false)}
className="flex-1 py-2 bg-muted rounded-lg text-sm"
>
Betal
{t('tokens.cancel')}
</button>
<button
onClick={handleAddToken}
disabled={!newAssetId}
className="flex-1 py-2 bg-cyan-600 rounded-lg text-sm disabled:opacity-50"
>
Zêde Bike
{t('tokens.add')}
</button>
</div>
</div>
@@ -667,7 +669,9 @@ export function TokensCard({ onSendToken }: Props) {
<div className="mx-4 mb-2 px-3 py-2 bg-green-500/10 border border-green-500/30 rounded-lg flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<div>
<p className="text-xs text-green-400 font-medium">Pezkuwichain Girêdayî</p>
<p className="text-xs text-green-400 font-medium">
{t('tokens.blockchainConnected')}
</p>
{endpointName && <p className="text-[10px] text-green-400/70">{endpointName}</p>}
</div>
</div>
@@ -675,9 +679,11 @@ export function TokensCard({ onSendToken }: Props) {
<div className="mx-4 mb-2 px-3 py-2 bg-yellow-500/10 border border-yellow-500/30 rounded-lg flex items-center gap-2">
<RefreshCw className="w-4 h-4 text-yellow-400 animate-spin flex-shrink-0" />
<div>
<p className="text-xs text-yellow-400 font-medium">Girêdana Blockchain...</p>
<p className="text-xs text-yellow-400 font-medium">
{t('tokens.connectingBlockchain')}
</p>
<p className="text-[10px] text-yellow-400/70">
{getLastError() || 'RPC serverê tê girêdan...'}
{getLastError() || t('tokens.connectingRpc')}
</p>
</div>
</div>
@@ -688,7 +694,7 @@ export function TokensCard({ onSendToken }: Props) {
{filteredTokens.length === 0 ? (
<div className="text-center py-8">
<Coins className="w-6 h-6 text-muted-foreground mx-auto mb-2" />
<p className="text-sm text-muted-foreground">Token nehat dîtin</p>
<p className="text-sm text-muted-foreground">{t('tokens.tokenNotFound')}</p>
</div>
) : (
filteredTokens.map((token) =>
@@ -793,7 +799,7 @@ export function TokensCard({ onSendToken }: Props) {
{/* Total Value */}
{token.valueUsd !== undefined && token.balance !== '--' && (
<div className="mt-2 pt-2 border-t border-white/10 flex justify-between items-center">
<span className="text-xs text-muted-foreground">Toplam</span>
<span className="text-xs text-muted-foreground">{t('tokens.total')}</span>
<span className="text-sm font-semibold">
$
{(
@@ -879,7 +885,9 @@ export function TokensCard({ onSendToken }: Props) {
{token.balance}
</p>
{token.balance === '--' ? (
<p className="text-xs text-muted-foreground"> barkirin...</p>
<p className="text-xs text-muted-foreground">
{t('tokens.loadingBalance')}
</p>
) : token.valueUsd !== undefined ? (
<p className="text-xs text-muted-foreground">
${token.valueUsd.toFixed(2)}
+72 -63
View File
@@ -29,6 +29,7 @@ import { HEZStakingModal } from './HEZStakingModal';
import { DepositUSDTModal } from './DepositUSDTModal';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
import { formatAddress } from '@/lib/wallet-service';
interface Props {
@@ -51,6 +52,7 @@ interface Transaction {
export function WalletDashboard({ onDisconnect }: Props) {
const { address, balance, api, assetHubApi, disconnect, isLoading } = useWallet();
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
const { t } = useTranslation();
const [copied, setCopied] = useState(false);
const [activeTab, setActiveTab] = useState<'main' | 'send' | 'receive' | 'history'>('main');
@@ -583,7 +585,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
)}
</button>
</div>
<p className="text-xs text-green-400">Girêdayî</p>
<p className="text-xs text-green-400">{t('dashboard.connected')}</p>
</div>
</div>
<button
@@ -644,7 +646,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
<div className="w-8 h-8 bg-green-500/20 rounded-full flex items-center justify-center">
<Send className="w-4 h-4 text-green-400" />
</div>
<span className="text-xs font-medium">Bişîne</span>
<span className="text-xs font-medium">{t('dashboard.send')}</span>
</button>
<button
@@ -657,7 +659,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
<div className="w-8 h-8 bg-cyan-500/20 rounded-full flex items-center justify-center">
<QrCode className="w-4 h-4 text-cyan-400" />
</div>
<span className="text-xs font-medium">Werbigire</span>
<span className="text-xs font-medium">{t('dashboard.receive')}</span>
</button>
<button
@@ -702,7 +704,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
<button
onClick={() => {
hapticImpact('light');
showAlert('Presale tê de ye! Zûtirîn demekê de dê bête vekirin.');
showAlert(t('dashboard.presaleMessage'));
}}
className="flex flex-col items-center gap-1 p-3 bg-gradient-to-r from-pink-600/20 to-red-500/20 border border-pink-500/30 rounded-xl"
>
@@ -726,10 +728,8 @@ export function WalletDashboard({ onDisconnect }: Props) {
<img src="/tokens/USDT.png" alt="USDT" className="w-8 h-8 rounded-full" />
</div>
<div className="flex-1 text-left">
<div className="font-semibold text-emerald-400">USDT Zêde Bike</div>
<div className="text-xs text-muted-foreground">
TON, Polkadot an TRC20 ji zincîrên din
</div>
<div className="font-semibold text-emerald-400">{t('dashboard.depositUsdt')}</div>
<div className="text-xs text-muted-foreground">{t('dashboard.depositUsdtDesc')}</div>
</div>
<ArrowDownLeft className="w-5 h-5 text-emerald-400" />
</button>
@@ -739,7 +739,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
<div className="px-4 pb-4">
<div className="bg-muted/50 border border-border rounded-xl p-4">
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold">Çalakiya Dawî</h3>
<h3 className="font-semibold">{t('dashboard.recentActivity')}</h3>
<div className="flex items-center gap-2">
<button
onClick={() => {
@@ -747,7 +747,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
setActiveTab('history');
}}
className="text-gray-400 hover:text-white p-1"
title="Dîrok"
title={t('dashboard.history')}
>
<History className="w-4 h-4" />
</button>
@@ -755,7 +755,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
onClick={handleRefresh}
disabled={isLoadingTxs}
className="text-gray-400 hover:text-white p-1"
title="Nûkirin"
title={t('dashboard.refreshTx')}
>
<RefreshCw className={`w-4 h-4 ${isLoadingTxs ? 'animate-spin' : ''}`} />
</button>
@@ -765,13 +765,13 @@ export function WalletDashboard({ onDisconnect }: Props) {
{isLoadingTxs ? (
<div className="text-center py-8">
<RefreshCw className="w-8 h-8 text-gray-600 mx-auto mb-2 animate-spin" />
<p className="text-gray-400 text-sm"> barkirin...</p>
<p className="text-gray-400 text-sm">{t('dashboard.loadingTx')}</p>
</div>
) : recentTxs.length === 0 ? (
<div className="text-center py-8">
<History className="w-8 h-8 text-gray-600 mx-auto mb-2" />
<p className="text-gray-500 text-sm">Transaksiyona dawî tune</p>
<p className="text-gray-600 text-xs mt-1">Dîroka te li vir xuya bibe</p>
<p className="text-gray-500 text-sm">{t('dashboard.noRecentTx')}</p>
<p className="text-gray-600 text-xs mt-1">{t('dashboard.historyAppears')}</p>
</div>
) : (
<div className="space-y-2">
@@ -792,7 +792,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
</div>
<div>
<div className="text-sm font-medium">
{tx.direction === 'sent' ? 'Şandin' : 'Wergirtin'}
{tx.direction === 'sent' ? t('dashboard.sent') : t('dashboard.received')}
</div>
<div className="text-xs text-gray-400">Block #{tx.blockNumber}</div>
</div>
@@ -852,7 +852,9 @@ export function WalletDashboard({ onDisconnect }: Props) {
{isStakingSelectorOpen && (
<div className="fixed inset-0 z-50 bg-black/80 flex items-end justify-center">
<div className="bg-background w-full max-w-lg rounded-t-3xl p-6 animate-in slide-in-from-bottom duration-300">
<h2 className="text-lg font-semibold mb-4 text-center">Staking Hilbijêre</h2>
<h2 className="text-lg font-semibold mb-4 text-center">
{t('dashboard.selectStaking')}
</h2>
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => {
@@ -865,8 +867,10 @@ export function WalletDashboard({ onDisconnect }: Props) {
<Coins className="w-6 h-6 text-green-400" />
</div>
<div className="font-medium">HEZ Staking</div>
<div className="text-xs text-muted-foreground mt-1">Validator nominate bike</div>
<div className="text-xs text-green-400 mt-2">Trust Score +</div>
<div className="text-xs text-muted-foreground mt-1">
{t('dashboard.validatorNominate')}
</div>
<div className="text-xs text-green-400 mt-2">{t('dashboard.trustScorePlus')}</div>
</button>
<button
@@ -880,8 +884,10 @@ export function WalletDashboard({ onDisconnect }: Props) {
<Droplets className="w-6 h-6 text-purple-400" />
</div>
<div className="font-medium">LP Staking</div>
<div className="text-xs text-muted-foreground mt-1">LP token stake bike</div>
<div className="text-xs text-purple-400 mt-2">PEZ Xelat +</div>
<div className="text-xs text-muted-foreground mt-1">
{t('dashboard.lpStakeDesc')}
</div>
<div className="text-xs text-purple-400 mt-2">{t('dashboard.pezRewardPlus')}</div>
</button>
</div>
@@ -889,7 +895,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
onClick={() => setIsStakingSelectorOpen(false)}
className="w-full mt-4 py-3 bg-secondary text-muted-foreground rounded-xl"
>
Paşve
{t('dashboard.goBack')}
</button>
</div>
</div>
@@ -948,6 +954,7 @@ const SEND_TOKENS: TokenOption[] = [
function SendTab({ onBack }: { onBack: () => void }) {
const { balance, api, assetHubApi, keypair } = useWallet();
const { hapticNotification, hapticImpact } = useTelegram();
const { t } = useTranslation();
const [selectedToken, setSelectedToken] = useState<SendToken | null>(null);
const [toAddress, setToAddress] = useState('');
@@ -1013,11 +1020,11 @@ function SendTab({ onBack }: { onBack: () => void }) {
const tg = window.Telegram?.WebApp;
if (!tg?.showScanQrPopup) {
setError('QR okuyucu vê platformê de amade nîne');
setError(t('send.qrNotAvailable'));
return;
}
tg.showScanQrPopup({ text: 'Navnîşana cîzdanê bişoxîne' }, (scannedText: string) => {
tg.showScanQrPopup({ text: t('send.scanQrText') }, (scannedText: string) => {
if (scannedText) {
// Extract address - might be raw address or URI format
let address = scannedText;
@@ -1035,7 +1042,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
tg.closeScanQrPopup?.();
return true; // Close popup
} else {
setError('Navnîşana derbasdar nîne');
setError(t('send.invalidAddress'));
hapticNotification('error');
}
}
@@ -1045,17 +1052,17 @@ function SendTab({ onBack }: { onBack: () => void }) {
const handleSend = async () => {
if (!keypair) {
setError('Cîzdan amade nîne');
setError(t('send.walletNotReady'));
return;
}
if (!selectedToken) {
setError('Token hilbijêre');
setError(t('send.selectTokenFirst'));
return;
}
if (!toAddress || !amount) {
setError('Navnîşan û mîqdar binivîse');
setError(t('send.fillAddressAndAmount'));
return;
}
@@ -1063,20 +1070,20 @@ function SendTab({ onBack }: { onBack: () => void }) {
const sendAmount = parseFloat(amount);
if (sendAmount > currentBalance) {
setError('Bakiye têrê nake');
setError(t('send.insufficientBalance'));
return;
}
// Check if appropriate API is available
if (selectedToken === 'HEZ' && !api) {
setError('Mainnet API amade nîne');
setError(t('send.mainnetApiNotReady'));
return;
}
if (
(selectedToken === 'PEZ' || selectedToken === 'USDT' || selectedToken === 'DOT') &&
!assetHubApi
) {
setError('Asset Hub API amade nîne');
setError(t('send.assetHubApiNotReady'));
return;
}
@@ -1106,7 +1113,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
setSuccess(true);
hapticNotification('success');
} catch (err) {
setError(err instanceof Error ? err.message : 'Transfer neserketî');
setError(err instanceof Error ? err.message : t('send.transferFailed'));
hapticNotification('error');
} finally {
setIsLoading(false);
@@ -1119,13 +1126,13 @@ function SendTab({ onBack }: { onBack: () => void }) {
<div className="p-4 space-y-6">
<div className="flex items-center justify-between">
<button onClick={onBack} className="text-muted-foreground">
Paş
{t('send.back')}
</button>
<h2 className="text-lg font-semibold">Token Hilbijêre</h2>
<h2 className="text-lg font-semibold">{t('send.selectToken')}</h2>
<div className="w-10" />
</div>
<p className="text-sm text-muted-foreground text-center">Kîjan token bişîne?</p>
<p className="text-sm text-muted-foreground text-center">{t('send.whichToken')}</p>
<div className="space-y-3">
{SEND_TOKENS.map((token) => {
@@ -1170,7 +1177,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
</div>
{(!api || !assetHubApi) && (
<p className="text-xs text-yellow-500 text-center"> Hinek chain girêdayî nîne</p>
<p className="text-xs text-yellow-500 text-center">{t('send.someChainNotConnected')}</p>
)}
</div>
);
@@ -1183,9 +1190,9 @@ function SendTab({ onBack }: { onBack: () => void }) {
<Check className="w-10 h-10 text-green-500" />
</div>
<div>
<h2 className="text-xl font-semibold mb-2">Transfer Serketî!</h2>
<h2 className="text-xl font-semibold mb-2">{t('send.transferSuccess')}</h2>
<p className="text-muted-foreground text-sm">
{amount} {selectedToken} hat şandin
{t('send.wasSent', { amount, token: selectedToken || '' })}
</p>
</div>
{txHash && (
@@ -1198,7 +1205,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
onClick={onBack}
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold"
>
Temam
{t('send.done')}
</button>
</div>
);
@@ -1210,9 +1217,11 @@ function SendTab({ onBack }: { onBack: () => void }) {
<div className="p-4 space-y-6">
<div className="flex items-center justify-between">
<button onClick={() => setSelectedToken(null)} className="text-muted-foreground">
Paş
{t('send.back')}
</button>
<h2 className="text-lg font-semibold">Bişîne {selectedToken}</h2>
<h2 className="text-lg font-semibold">
{t('send.sendToken', { token: selectedToken || '' })}
</h2>
<div className="w-10" />
</div>
@@ -1231,13 +1240,13 @@ function SendTab({ onBack }: { onBack: () => void }) {
onClick={() => setSelectedToken(null)}
className="text-xs text-primary hover:underline"
>
Biguhere
{t('send.changeToken')}
</button>
</div>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Navnîşana Wergir</label>
<label className="text-sm text-muted-foreground">{t('send.recipientAddress')}</label>
<div className="flex gap-2">
<input
type="text"
@@ -1249,7 +1258,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
<button
onClick={handleScanQR}
className="px-4 py-3 bg-cyan-600 hover:bg-cyan-700 rounded-xl flex items-center justify-center"
title="QR Bişoxîne"
title={t('send.scanQr')}
>
<ScanLine className="w-5 h-5" />
</button>
@@ -1258,9 +1267,9 @@ function SendTab({ onBack }: { onBack: () => void }) {
<div className="space-y-2">
<div className="flex justify-between">
<label className="text-sm text-muted-foreground">Mîqdar (HEZ)</label>
<label className="text-sm text-muted-foreground">{t('send.amount')}</label>
<span className="text-sm text-muted-foreground">
Bakiye: {getCurrentBalance()} {selectedToken}
{t('send.balanceLabel')} {getCurrentBalance()} {selectedToken}
</span>
</div>
<input
@@ -1287,12 +1296,12 @@ function SendTab({ onBack }: { onBack: () => void }) {
{isLoading ? (
<>
<RefreshCw className="w-4 h-4 animate-spin" />
şandin...
{t('send.sending')}
</>
) : (
<>
<Send className="w-4 h-4" />
Bişîne
{t('send.sendButton')}
</>
)}
</button>
@@ -1307,6 +1316,7 @@ function ReceiveTab({ address, onBack }: { address: string | null; onBack: () =>
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);
const [qrError, setQrError] = useState(false);
const { hapticNotification } = useTelegram();
const { t } = useTranslation();
// Generate QR code when address changes
useEffect(() => {
@@ -1344,9 +1354,9 @@ function ReceiveTab({ address, onBack }: { address: string | null; onBack: () =>
<div className="p-4 space-y-6">
<div className="flex items-center justify-between">
<button onClick={onBack} className="text-muted-foreground">
Paş
{t('receive.back')}
</button>
<h2 className="text-lg font-semibold">Werbigire</h2>
<h2 className="text-lg font-semibold">{t('receive.title')}</h2>
<div className="w-10" />
</div>
@@ -1358,16 +1368,14 @@ function ReceiveTab({ address, onBack }: { address: string | null; onBack: () =>
) : qrError ? (
<div className="text-center">
<QrCode className="w-16 h-16 text-gray-400 mx-auto mb-2" />
<p className="text-xs text-gray-500">QR çênebû</p>
<p className="text-xs text-gray-500">{t('receive.qrFailed')}</p>
</div>
) : (
<div className="animate-pulse bg-gray-200 w-full h-full rounded-lg" />
)}
</div>
<p className="text-sm text-muted-foreground">
Ev navnîşana te ye. Ji bo wergirtina token navnîşanê parve bike.
</p>
<p className="text-sm text-muted-foreground">{t('receive.shareAddress')}</p>
<div className="p-4 bg-muted rounded-xl">
<p className="font-mono text-sm break-all">{address}</p>
@@ -1380,12 +1388,12 @@ function ReceiveTab({ address, onBack }: { address: string | null; onBack: () =>
{copied ? (
<>
<Check className="w-4 h-4" />
Hat kopîkirin!
{t('receive.addressCopied')}
</>
) : (
<>
<Copy className="w-4 h-4" />
Navnîşanê Kopî Bike
{t('receive.copyAddress')}
</>
)}
</button>
@@ -1407,6 +1415,7 @@ function HistoryTab({
onBack: () => void;
}) {
const { hapticImpact } = useTelegram();
const { t } = useTranslation();
const getDecimalsForAsset = (section: string, assetId?: string): number => {
if (section === 'balances') return 12; // HEZ
@@ -1433,9 +1442,9 @@ function HistoryTab({
<div className="p-4 space-y-4">
<div className="flex items-center justify-between">
<button onClick={onBack} className="text-muted-foreground">
Paş
{t('history.back')}
</button>
<h2 className="text-lg font-semibold">Dîroka Transaksiyonan</h2>
<h2 className="text-lg font-semibold">{t('history.title')}</h2>
<button
onClick={() => {
hapticImpact('light');
@@ -1451,13 +1460,13 @@ function HistoryTab({
{isLoading ? (
<div className="text-center py-12">
<RefreshCw className="w-10 h-10 text-gray-600 mx-auto mb-3 animate-spin" />
<p className="text-gray-400"> barkirin...</p>
<p className="text-gray-400">{t('history.loadingTx')}</p>
</div>
) : transactions.length === 0 ? (
<div className="text-center py-12">
<History className="w-12 h-12 text-gray-600 mx-auto mb-3" />
<p className="text-gray-400 mb-1">Transaksiyona tune</p>
<p className="text-gray-600 text-sm">Dîroka te li vir xuya bibe</p>
<p className="text-gray-400 mb-1">{t('history.noTransactions')}</p>
<p className="text-gray-600 text-sm">{t('history.historyAppears')}</p>
</div>
) : (
<div className="space-y-3">
@@ -1479,7 +1488,7 @@ function HistoryTab({
</div>
<div>
<div className="font-medium">
{tx.direction === 'sent' ? 'Şandin' : 'Wergirtin'}
{tx.direction === 'sent' ? t('history.sent') : t('history.received')}
</div>
<div className="text-xs text-gray-400">Block #{tx.blockNumber}</div>
</div>
@@ -1504,12 +1513,12 @@ function HistoryTab({
<div className="pt-2 border-t border-border/50 space-y-1">
{tx.direction === 'sent' ? (
<div className="flex justify-between text-xs">
<span className="text-gray-500">Ji bo:</span>
<span className="text-gray-500">{t('history.to')}</span>
<span className="font-mono text-gray-400">{formatAddress(tx.to || '')}</span>
</div>
) : (
<div className="flex justify-between text-xs">
<span className="text-gray-500">Ji:</span>
<span className="text-gray-500">{t('history.from')}</span>
<span className="font-mono text-gray-400">{formatAddress(tx.from)}</span>
</div>
)}
+3 -2
View File
@@ -5,6 +5,7 @@
*/
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
import { translate } from '@/i18n';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import {
@@ -53,7 +54,7 @@ export function ReferralProvider({ children }: { children: ReactNode }) {
setMyReferrals(fetchedReferrals);
} catch (error) {
console.error('Error fetching referral stats:', error);
showAlert('Referral stats bar nekirin');
showAlert(translate('context.referralStatsError'));
} finally {
setLoading(false);
}
@@ -75,7 +76,7 @@ export function ReferralProvider({ children }: { children: ReactNode }) {
if (event.referrer === address || event.referred === address) {
if (event.type === 'confirmed') {
hapticNotification('success');
showAlert(`Referral hat pejirandin! Hejmara te: ${event.count}`);
showAlert(translate('context.referralApproved', { count: event.count ?? 0 }));
}
fetchStats();
}
+7 -6
View File
@@ -24,6 +24,7 @@ import {
import { validatePassword } from '@/lib/crypto';
import { supabase } from '@/lib/supabase';
import { useAuth } from './AuthContext';
import { translate } from '@/i18n';
import {
initRPCConnection,
subscribeToConnection,
@@ -123,7 +124,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
});
} catch (err) {
console.error('Wallet init error:', err);
setError('Wallet dest pê nekir');
setError(translate('context.walletInitFailed'));
setIsLoading(false);
}
};
@@ -138,7 +139,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
setError(null);
} else if (wasConnected && !connected) {
// Only show disconnect error if we were previously connected
setError('Têkiliya RPC qut bû. Dîsa girêdan tê kirin...');
setError(translate('context.rpcDisconnected'));
}
});
@@ -195,7 +196,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
async (mnemonic: string, password: string): Promise<void> => {
// User must be authenticated first
if (!isAuthenticated || !user?.telegram_id) {
throw new Error('Ji kerema xwe pêşî têkeve');
throw new Error(translate('context.pleaseLoginFirst'));
}
const validation = validatePassword(password);
@@ -221,11 +222,11 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
async (mnemonic: string, password: string): Promise<string> => {
// User must be authenticated first
if (!isAuthenticated || !user?.telegram_id) {
throw new Error('Ji kerema xwe pêşî têkeve');
throw new Error(translate('context.pleaseLoginFirst'));
}
if (!validateMnemonic(mnemonic)) {
throw new Error('Seed phrase ne derbasdar e');
throw new Error(translate('context.invalidSeedPhrase'));
}
const validation = validatePassword(password);
@@ -258,7 +259,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) {
setKeypair(pair);
setIsConnected(true);
} catch (err) {
const message = err instanceof Error ? err.message : 'Girêdan neserketî';
const message = err instanceof Error ? err.message : translate('context.connectionFailed');
setError(message);
throw err;
} finally {
+38
View File
@@ -17,6 +17,39 @@ const translations: Record<LanguageCode, Translations> = {
ar,
};
// Module-level language tracking for non-React contexts
let currentLanguage: LanguageCode = 'krd';
/**
* Standalone translate function for non-React files (utils, crypto, error-tracking, etc.)
* Language is synced from LanguageProvider automatically.
*/
export function translate(key: string, params?: Record<string, string | number>): string {
let value = getNestedValue(
translations[currentLanguage] as unknown as Record<string, unknown>,
key
);
if (value === undefined && currentLanguage !== DEFAULT_LANG) {
value = getNestedValue(translations[DEFAULT_LANG] as unknown as Record<string, unknown>, key);
}
if (value === undefined) return key;
if (params) {
let result = value;
for (const [paramKey, paramValue] of Object.entries(params)) {
result = result.replace(`{${paramKey}}`, String(paramValue));
}
return result;
}
return value;
}
/** Get current language code (for locale-dependent formatting) */
export function getCurrentLanguage(): LanguageCode {
return currentLanguage;
}
const VALID_LANGS: LanguageCode[] = ['krd', 'en', 'tr', 'ckb', 'fa', 'ar'];
const DEFAULT_LANG: LanguageCode = 'krd';
@@ -96,6 +129,11 @@ export function LanguageProvider({ children }: LanguageProviderProps) {
const isRTL = RTL_LANGUAGES.includes(lang);
// Sync module-level language for standalone translate()
useEffect(() => {
currentLanguage = lang;
}, [lang]);
// Update document direction, lang attribute, and URL when language changes
useEffect(() => {
document.documentElement.lang = lang === 'krd' ? 'ku' : lang;
+275
View File
@@ -299,6 +299,281 @@ const ar: Translations = {
loadingScreen: {
loading: 'جارٍ التحميل...',
},
// Dashboard
dashboard: {
connected: 'متصل',
send: 'إرسال',
receive: 'استلام',
presaleMessage: 'البيع المسبق قادم! سيفتح قريباً جداً.',
depositUsdt: 'إضافة USDT',
depositUsdtDesc: 'من شبكات TON أو Polkadot أو TRC20',
recentActivity: 'النشاط الأخير',
history: 'السجل',
refreshTx: 'تحديث',
loadingTx: 'جاري التحميل...',
noRecentTx: 'لا توجد معاملات حديثة',
historyAppears: 'سيظهر سجلك هنا',
sent: 'تم الإرسال',
received: 'تم الاستلام',
selectStaking: 'اختيار Staking',
validatorNominate: 'ترشيح المدقق',
trustScorePlus: 'Trust Score +',
lpStakeDesc: 'رهن LP token',
pezRewardPlus: 'مكافأة PEZ +',
goBack: 'رجوع',
},
// Send
send: {
back: '← رجوع',
selectToken: 'اختيار Token',
whichToken: 'أي token تريد إرساله؟',
someChainNotConnected: '⚠️ بعض السلاسل غير متصلة',
transferSuccess: 'تم التحويل بنجاح!',
wasSent: 'تم إرسال {amount} {token}',
done: 'تم',
sendToken: 'إرسال {token}',
changeToken: 'تغيير',
recipientAddress: 'عنوان المستلم',
scanQr: 'مسح QR',
scanQrText: 'امسح عنوان المحفظة',
qrNotAvailable: 'ماسح QR غير متاح على هذه المنصة',
invalidAddress: 'عنوان غير صالح',
amount: 'المبلغ',
balanceLabel: 'الرصيد:',
sending: 'جاري الإرسال...',
sendButton: 'إرسال',
walletNotReady: 'المحفظة غير جاهزة',
selectTokenFirst: 'اختر token',
fillAddressAndAmount: 'أدخل العنوان والمبلغ',
insufficientBalance: 'رصيد غير كافٍ',
mainnetApiNotReady: 'Mainnet API غير جاهز',
assetHubApiNotReady: 'Asset Hub API غير جاهز',
transferFailed: 'فشل التحويل',
},
// Receive
receive: {
title: 'استلام',
back: '← رجوع',
qrFailed: 'فشل إنشاء QR',
shareAddress: 'هذا عنوانك. شاركه لاستلام التوكن.',
addressCopied: 'تم النسخ!',
copyAddress: 'نسخ العنوان',
},
// History
history: {
title: 'سجل المعاملات',
back: '← رجوع',
loadingTx: 'جاري التحميل...',
noTransactions: 'لا توجد معاملات',
historyAppears: 'سيظهر سجلك هنا',
sent: 'تم الإرسال',
received: 'تم الاستلام',
to: 'إلى:',
from: 'من:',
},
// Swap
swap: {
title: 'Token Swap',
fromLabel: 'من',
toLabel: 'إلى',
exchangeRate: 'سعر الصرف',
noPool: 'لا يوجد Pool',
swapping: 'جاري التبديل...',
noPoolButton: 'لا يوجد Pool',
swapButton: 'Swap',
swapFailed: 'فشل Swap',
swapSuccess: 'تم Swap بنجاح!',
balanceLabel: 'الرصيد:',
insufficientBalance: 'رصيد غير كافٍ',
},
// Pools
pools: {
title: 'مجمعات السيولة',
connectionError: 'خطأ في الاتصال - أعد المحاولة',
loadingPools: 'جاري التحميل...',
noPools: 'لا يوجد Pool',
back: '← رجوع',
addLiquidity: 'إضافة سيولة',
removeLiquidity: 'سحب سيولة',
yourPosition: 'موقعك',
addButton: 'إضافة',
removeButton: 'سحب',
reserve: 'احتياطي',
lpBalance: 'LP رصيد:',
lpTokenAmount: 'مبلغ LP Token',
amountAuto: '{token} مبلغ (تلقائي)',
adding: 'جاري الإضافة...',
removing: 'جاري السحب...',
addFailed: 'فشلت الإضافة',
removeFailed: 'فشل السحب',
invalidLpAmount: 'مبلغ LP غير صالح',
estimatedReturn: 'العائد المقدر:',
success: 'نجاح!',
addedLiquidity: 'تمت إضافة {amount0} {token0} + {amount1} {token1}',
removedLiquidity: 'تم إرجاع {amount} LP token',
},
// Staking
staking: {
palletNotFound: 'وحدة Staking غير موجودة',
fetchError: 'فشل جلب معلومات staking',
statusTab: 'الحالة',
bondTab: 'Bond',
nominateTab: 'ترشيح',
unbondTab: 'Unbond',
activeStake: 'Stake نشط',
totalBonded: 'إجمالي Bonded',
nominations: 'الترشيحات',
rewardDestination: 'وجهة المكافأة',
unbondingChunks: 'Unbonding',
nominatedValidators: 'المدققون المرشحون',
stakingTip: 'رهن HEZ يزيد درجة الثقة. ارهن لمدة شهر واحد على الأقل للمكافأة.',
notStakedYet: 'لم يتم الرهن بعد',
stakeForTrustScore: 'ارهن HEZ لكسب Trust Score',
startStaking: 'ابدأ',
yourBalance: 'رصيدك',
currentlyStaked: 'مرهون حالياً',
amountHez: 'المبلغ (HEZ)',
bondWarning: 'الرهن يتطلب 28 يوماً للسحب.',
bonding: 'جاري Bond...',
bondExtra: 'إضافة المزيد',
bondButton: 'Bond',
bondFirst: 'أولاً bond HEZ، ثم رشّح',
selectValidators: 'اختر مدققين (حد أقصى 16): {count}/16',
commission: 'العمولة:',
nominating: 'جاري الترشيح...',
nominateButton: 'رشّح',
notStakedUnbond: 'لم تقم بالرهن بعد',
unbondWarning: 'Unbond يستغرق 28 يوماً. بعدها يمكنك السحب.',
unbondProcessing: 'جاري Unbond...',
unbondButton: 'Unbond',
bondSuccess: 'تم رهن {amount} HEZ بنجاح!',
nominateSuccess: 'تم ترشيح {count} مدقق بنجاح!',
unbondSuccess: 'تم unbond {amount} HEZ! (28 يوم انتظار)',
bondFailed: 'فشل Bond',
nominateFailed: 'فشل الترشيح',
unbondFailed: 'فشل Unbond',
},
// LP Staking
lpStaking: {
palletNotReady: 'وحدة Staking غير جاهزة',
poolsNotLoaded: 'فشل تحميل pool Staking',
noPoolsYet: 'لا توجد pool staking بعد',
selectPool: 'اختر Pool',
totalStaked: 'إجمالي Staked',
youStaked: 'أنت رهنت',
lpBalance: 'LP رصيد',
reward: 'مكافأة',
stakeTab: 'Stake',
unstakeTab: 'Unstake',
rewardTab: 'مكافأة',
amount: 'مبلغ ({asset})',
balanceLabel: 'الرصيد:',
stakedLabel: 'Staked:',
staking: 'جاري Stake...',
stakeButton: 'Stake',
unstaking: 'جاري Unstake...',
unstakeButton: 'Unstake',
pendingRewards: 'مكافآت معلقة',
claiming: 'جاري الاستلام...',
claimButton: 'استلم المكافآت',
stakeFailed: 'فشل Stake',
stakeSuccess: 'تم Stake بنجاح!',
unstakeFailed: 'فشل Unstake',
unstakeSuccess: 'تم Unstake بنجاح!',
claimFailed: 'فشل استلام المكافأة',
claimSuccess: 'تم استلام المكافآت!',
},
// Fees
fees: {
title: 'إضافة رسوم',
subtitle: 'HEZ teleport',
success: 'نجاح!',
sentTo: 'تم إرسال {amount} HEZ إلى {chain}',
failed: 'فشل',
tryAgain: 'أعد المحاولة',
targetChain: 'السلسلة المستهدفة',
forTransfers: 'لتحويل PEZ',
forIdentity: 'للهوية',
minRecommended: '{description} يوصى بحد أدنى 0.1 HEZ.',
amountHez: 'المبلغ (HEZ)',
signing: 'وقّع المعاملة...',
xcmTeleportPending: 'XCM Teleport قيد التنفيذ...',
signingButton: 'جاري التوقيع...',
processing: 'جاري المعالجة...',
sendTo: 'إرسال إلى {chain}',
walletNotConnected: 'المحفظة غير متصلة',
apiNotConnected: 'API غير متصل',
enterValidAmount: 'أدخل مبلغاً صالحاً',
chainNotConnected: 'السلسلة غير متصلة',
insufficientBalance: 'رصيد غير كافٍ',
xcmPalletNotFound: 'وحدة XCM غير موجودة',
teleportFailed: 'فشل Teleport',
errorOccurred: 'حدث خطأ',
},
// Tokens
tokens: {
searchPlaceholder: 'بحث token...',
addToken: 'إضافة Token',
assetIdPlaceholder: 'أدخل Asset ID (مثال: 3)',
cancel: 'إلغاء',
add: 'إضافة',
blockchainConnected: 'Pezkuwichain متصل',
connectingBlockchain: 'الاتصال بـ Blockchain...',
connectingRpc: 'الاتصال بخادم RPC...',
tokenNotFound: 'Token غير موجود',
total: 'الإجمالي',
loadingBalance: 'جاري التحميل...',
},
// Errors
errors: {
networkError: 'لا يوجد اتصال بالإنترنت. يرجى التحقق من اتصالك.',
timeout: 'استغرقت العملية وقتاً طويلاً. يرجى المحاولة مرة أخرى.',
walletNotFound: 'المحفظة غير موجودة. يرجى إنشاء محفظة أو استعادتها.',
wrongPassword: 'كلمة مرور خاطئة. يرجى المحاولة مرة أخرى.',
default: 'حدث خطأ ما. يرجى المحاولة مرة أخرى.',
},
// Validation
validation: {
minLength: 'كلمة المرور 12 حرفاً على الأقل',
needLowercase: 'كلمة المرور تحتوي على حرف صغير واحد على الأقل (a-z)',
needUppercase: 'كلمة المرور تحتوي على حرف كبير واحد على الأقل (A-Z)',
needNumber: 'كلمة المرور تحتوي على رقم واحد على الأقل (0-9)',
needSpecialChar: 'كلمة المرور تحتوي على رمز خاص واحد على الأقل (!@#$%...)',
weakPassword: 'كلمة المرور ليست قوية بما يكفي. جرب كلمة مرور أطول بأحرف متنوعة.',
},
// Time
time: {
now: 'الآن',
minutesAgo: 'منذ {count} دقيقة',
hoursAgo: 'منذ {count} ساعة',
daysAgo: 'منذ {count} يوم',
},
// Context
context: {
walletInitFailed: 'فشل تهيئة المحفظة',
rpcDisconnected: 'انقطع اتصال RPC. جاري إعادة الاتصال...',
pleaseLoginFirst: 'يرجى تسجيل الدخول أولاً',
invalidSeedPhrase: 'Seed phrase غير صالح',
connectionFailed: 'فشل الاتصال',
referralStatsError: 'فشل تحميل إحصائيات الإحالة',
referralApproved: 'تمت الموافقة على الإحالة! عددك: {count}',
wrongPasswordError: 'كلمة مرور خاطئة',
walletSyncFailed: 'فشل مزامنة عنوان المحفظة مع قاعدة البيانات',
},
};
export default ar;
+275
View File
@@ -301,6 +301,281 @@ const ckb: Translations = {
loadingScreen: {
loading: 'بارکردن...',
},
// Dashboard
dashboard: {
connected: 'پەیوەندی کراوە',
send: 'ناردن',
receive: 'وەرگرتن',
presaleMessage: 'پرێسەیل هاتووە! زوو دەکرێتەوە.',
depositUsdt: 'USDT زیاد بکە',
depositUsdtDesc: 'لە TON، Polkadot یان TRC20',
recentActivity: 'چالاکیە تازەکان',
history: 'مێژوو',
refreshTx: 'نوێکردنەوە',
loadingTx: 'بارکردن...',
noRecentTx: 'مامەڵەی تازە نییە',
historyAppears: 'مێژووت لێرە دەردەکەوێت',
sent: 'نێردرا',
received: 'وەرگیرا',
selectStaking: 'Staking هەڵبژێرە',
validatorNominate: 'Validator ناسێنە',
trustScorePlus: 'Trust Score +',
lpStakeDesc: 'LP token stake بکە',
pezRewardPlus: 'PEZ پاداشت +',
goBack: 'گەڕانەوە',
},
// Send
send: {
back: '← گەڕانەوە',
selectToken: 'Token هەڵبژێرە',
whichToken: 'کام token بنێرە؟',
someChainNotConnected: '⚠️ هەندێ chain پەیوەندی نییە',
transferSuccess: 'ناردن سەرکەوتوو بوو!',
wasSent: '{amount} {token} نێردرا',
done: 'تەواو',
sendToken: 'بنێرە {token}',
changeToken: 'بیگۆرە',
recipientAddress: 'ناونیشانی وەرگر',
scanQr: 'QR بخوێنەوە',
scanQrText: 'ناونیشانی جزدان بخوێنەوە',
qrNotAvailable: 'QR خوێنەرەوە لەم سیستەمەدا ئامادە نییە',
invalidAddress: 'ناونیشانی نادروست',
amount: 'بڕ',
balanceLabel: 'باڵانس:',
sending: 'دەنێردرێت...',
sendButton: 'بنێرە',
walletNotReady: 'جزدان ئامادە نییە',
selectTokenFirst: 'Token هەڵبژێرە',
fillAddressAndAmount: 'ناونیشان و بڕ بنووسە',
insufficientBalance: 'باڵانس بەسنییە',
mainnetApiNotReady: 'Mainnet API ئامادە نییە',
assetHubApiNotReady: 'Asset Hub API ئامادە نییە',
transferFailed: 'ناردن سەرنەکەوت',
},
// Receive
receive: {
title: 'وەرگرتن',
back: '← گەڕانەوە',
qrFailed: 'QR دروست نەبوو',
shareAddress: 'ئەمە ناونیشانتە. بۆ وەرگرتنی token ئەم ناونیشانە بڵاو بکەوە.',
addressCopied: 'کۆپی کرا!',
copyAddress: 'ناونیشان کۆپی بکە',
},
// History
history: {
title: 'مێژووی مامەڵەکان',
back: '← گەڕانەوە',
loadingTx: 'بارکردن...',
noTransactions: 'مامەڵە نییە',
historyAppears: 'مێژووت لێرە دەردەکەوێت',
sent: 'نێردرا',
received: 'وەرگیرا',
to: 'بۆ:',
from: 'لە:',
},
// Swap
swap: {
title: 'Token Swap',
fromLabel: 'لە',
toLabel: 'بۆ',
exchangeRate: 'ڕێژەی گۆڕین',
noPool: 'Pool نییە',
swapping: 'دەگۆڕدرێت...',
noPoolButton: 'Pool نییە',
swapButton: 'Swap بکە',
swapFailed: 'Swap سەرنەکەوت',
swapSuccess: 'Swap سەرکەوتوو بوو!',
balanceLabel: 'باڵانس:',
insufficientBalance: 'باڵانس بەسنییە',
},
// Pools
pools: {
title: 'Liquidity Pools',
connectionError: 'هەڵەی پەیوەندی - دوبارە هەوڵ بدەوە',
loadingPools: 'بارکردن...',
noPools: 'Pool نییە',
back: '← گەڕانەوە',
addLiquidity: 'Liquidity زیاد بکە',
removeLiquidity: 'Liquidity دەربهێنە',
yourPosition: 'پۆزیشنی تۆ',
addButton: 'زیاد بکە',
removeButton: 'دەربهێنە',
reserve: 'ئەندۆخته',
lpBalance: 'LP باڵانس:',
lpTokenAmount: 'LP Token بڕ',
amountAuto: '{token} بڕ (خۆکار)',
adding: 'زیاد دەکرێت...',
removing: 'دەردەهێنرێت...',
addFailed: 'زیادکردن سەرنەکەوت',
removeFailed: 'دەرهێنان سەرنەکەوت',
invalidLpAmount: 'بڕی LP نادروستە',
estimatedReturn: 'تەخمینی گەڕانەوە:',
success: 'سەرکەوتوو!',
addedLiquidity: '{amount0} {token0} + {amount1} {token1} زیاد کرا',
removedLiquidity: '{amount} LP token گەڕێنرایەوە',
},
// Staking
staking: {
palletNotFound: 'Staking pallet نەدۆزرایەوە',
fetchError: 'زانیاریی staking نەهاتە دەست',
statusTab: 'بارودۆخ',
bondTab: 'Bond',
nominateTab: 'Nominate',
unbondTab: 'Unbond',
activeStake: 'Stake چالاک',
totalBonded: 'کۆی Bonded',
nominations: 'ناساندنەکان',
rewardDestination: 'مەبەستی پاداشت',
unbondingChunks: 'Unbonding',
nominatedValidators: 'Validator ناسێنراوەکان',
stakingTip: 'HEZ stake کردن Trust Score زیاد دەکات. لانیکەم ١ مانگ stake بکە بۆ بۆنەس.',
notStakedYet: 'هێشتا Stake نەکراوە',
stakeForTrustScore: 'HEZ stake بکە بۆ بەدەستهێنانی Trust Score',
startStaking: 'دەست پێ بکە',
yourBalance: 'باڵانسی تۆ',
currentlyStaked: 'ئێستا Staked',
amountHez: 'بڕ (HEZ)',
bondWarning: 'Stake کردن ٢٨ ڕۆژ چاوەڕوانییە بۆ دەرهێنان.',
bonding: 'Bond دەکرێت...',
bondExtra: 'زیاد بکە',
bondButton: 'Bond بکە',
bondFirst: 'سەرەتا HEZ bond بکە، دواتر nominate بکە',
selectValidators: 'Validator هەڵبژێرە (زۆرینە ١٦): {count}/16',
commission: 'کۆمیسیۆن:',
nominating: 'Nominate دەکرێت...',
nominateButton: 'Nominate بکە',
notStakedUnbond: 'هێشتا stake نەکراوە',
unbondWarning: 'Unbond کردن ٢٨ ڕۆژ دەخایەنێت. دواتر دەتوانیت دەربهێنیت.',
unbondProcessing: 'Unbond دەکرێت...',
unbondButton: 'Unbond بکە',
bondSuccess: '{amount} HEZ بە سەرکەوتوویی stake کرا!',
nominateSuccess: '{count} validator بە سەرکەوتوویی nominate کران!',
unbondSuccess: '{amount} HEZ unbond کرا! (٢٨ ڕۆژ چاوەڕوانی)',
bondFailed: 'Bond سەرنەکەوت',
nominateFailed: 'Nominate سەرنەکەوت',
unbondFailed: 'Unbond سەرنەکەوت',
},
// LP Staking
lpStaking: {
palletNotReady: 'Staking pallet ئامادە نییە',
poolsNotLoaded: 'Staking pools بار نەکران',
noPoolsYet: 'هێشتا staking pool نییە',
selectPool: 'Pool هەڵبژێرە',
totalStaked: 'کۆی Staked',
youStaked: 'تۆ Stake کردووە',
lpBalance: 'LP باڵانس',
reward: 'پاداشت',
stakeTab: 'Stake',
unstakeTab: 'Unstake',
rewardTab: 'پاداشت',
amount: 'بڕ ({asset})',
balanceLabel: 'باڵانس:',
stakedLabel: 'Staked:',
staking: 'Stake دەکرێت...',
stakeButton: 'Stake بکە',
unstaking: 'Unstake دەکرێت...',
unstakeButton: 'Unstake بکە',
pendingRewards: 'پاداشتە چاوەڕوانەکان',
claiming: 'وەردەگیرێت...',
claimButton: 'پاداشتەکان وەربگرە',
stakeFailed: 'Stake سەرنەکەوت',
stakeSuccess: 'Stake سەرکەوتوو بوو!',
unstakeFailed: 'Unstake سەرنەکەوت',
unstakeSuccess: 'Unstake سەرکەوتوو بوو!',
claimFailed: 'پاداشت وەرنەگیرا',
claimSuccess: 'پاداشت وەرگیرا!',
},
// Fees
fees: {
title: 'Fee زیاد بکە',
subtitle: 'HEZ teleport',
success: 'سەرکەوتوو!',
sentTo: '{amount} HEZ بۆ {chain} نێردرا',
failed: 'سەرنەکەوت',
tryAgain: 'دوبارە هەوڵ بدەوە',
targetChain: 'زنجیرەی ئامانج',
forTransfers: 'بۆ ناردنی PEZ',
forIdentity: 'بۆ ناسنامە',
minRecommended: '{description} لانیکەم ٠.١ HEZ پێشنیار دەکرێت.',
amountHez: 'بڕ (HEZ)',
signing: 'مامەڵە واژوو بکە...',
xcmTeleportPending: 'XCM Teleport بەردەوامە...',
signingButton: 'واژوو دەکرێت...',
processing: 'کاردەکرێت...',
sendTo: 'بۆ {chain} بنێرە',
walletNotConnected: 'جزدان پەیوەندی نییە',
apiNotConnected: 'API پەیوەندی نییە',
enterValidAmount: 'بڕێکی دروست بنووسە',
chainNotConnected: 'زنجیرە پەیوەندی نییە',
insufficientBalance: 'باڵانس بەسنییە',
xcmPalletNotFound: 'XCM pallet نەدۆزرایەوە',
teleportFailed: 'Teleport سەرنەکەوت',
errorOccurred: 'هەڵەیەک ڕوویدا',
},
// Tokens
tokens: {
searchPlaceholder: 'Token بگەڕێ...',
addToken: 'Token زیاد بکە',
assetIdPlaceholder: 'Asset ID بنووسە (نموونە: 3)',
cancel: 'هەڵوەشاندنەوە',
add: 'زیاد بکە',
blockchainConnected: 'Pezkuwichain پەیوەندی کراوە',
connectingBlockchain: 'پەیوەندی بە Blockchain...',
connectingRpc: 'پەیوەندی بە RPC...',
tokenNotFound: 'Token نەدۆزرایەوە',
total: 'کۆی گشتی',
loadingBalance: 'بارکردن...',
},
// Errors
errors: {
networkError: 'پەیوەندی ئینتەرنێت نییە. تکایە پەیوەندیت بپشکنە.',
timeout: 'کارەکە زۆر درێژ کێشا. تکایە دوبارە هەوڵ بدەوە.',
walletNotFound: 'جزدان نەدۆزرایەوە. تکایە جزدان دروست بکە یان گەڕاندنەوە بکە.',
wrongPassword: 'وشەی نهێنی هەڵەیە. تکایە دوبارە هەوڵ بدەوە.',
default: 'شتێک هەڵەی کرد. تکایە دوبارە هەوڵ بدەوە.',
},
// Validation
validation: {
minLength: 'وشەی نهێنی لانیکەم ١٢ پیت بێت',
needLowercase: 'وشەی نهێنی لانیکەم ١ پیتی بچووک هەبێت (a-z)',
needUppercase: 'وشەی نهێنی لانیکەم ١ پیتی گەورە هەبێت (A-Z)',
needNumber: 'وشەی نهێنی لانیکەم ١ ژمارە هەبێت (0-9)',
needSpecialChar: 'وشەی نهێنی لانیکەم ١ هێمای تایبەت هەبێت (!@#$%...)',
weakPassword: 'وشەی نهێنی بەس بەهێز نییە. وشەیەکی نهێنی درێژتر بە پیتی جۆراوجۆر تاقی بکەوە.',
},
// Time
time: {
now: 'ئێستا',
minutesAgo: '{count} خولەک پێش',
hoursAgo: '{count} کاتژمێر پێش',
daysAgo: '{count} ڕۆژ پێش',
},
// Context
context: {
walletInitFailed: 'جزدان دەست پێ نەکرد',
rpcDisconnected: 'پەیوەندیی RPC پچڕا. دوبارە پەیوەندی دەکرێت...',
pleaseLoginFirst: 'تکایە سەرەتا بچۆ ژوورەوە',
invalidSeedPhrase: 'Seed phrase نادروستە',
connectionFailed: 'پەیوەندی سەرنەکەوت',
referralStatsError: 'ئامارەکانی referral بار نەکران',
referralApproved: 'Referral پەسەند کرا! ژمارەت: {count}',
wrongPasswordError: 'وشەی نهێنی هەڵەیە',
walletSyncFailed: 'هاوکاتکردنی ناونیشانی جزدان لەگەڵ DB سەرنەکەوت',
},
};
export default ckb;
+275
View File
@@ -300,6 +300,281 @@ const en: Translations = {
loadingScreen: {
loading: 'Loading...',
},
// Dashboard
dashboard: {
connected: 'Connected',
send: 'Send',
receive: 'Receive',
presaleMessage: 'Presale is coming! It will open very soon.',
depositUsdt: 'Add USDT',
depositUsdtDesc: 'From TON, Polkadot or TRC20 networks',
recentActivity: 'Recent Activity',
history: 'History',
refreshTx: 'Refresh',
loadingTx: 'Loading...',
noRecentTx: 'No recent transactions',
historyAppears: 'Your history will appear here',
sent: 'Sent',
received: 'Received',
selectStaking: 'Select Staking',
validatorNominate: 'Nominate validators',
trustScorePlus: 'Trust Score +',
lpStakeDesc: 'Stake LP tokens',
pezRewardPlus: 'PEZ Reward +',
goBack: 'Back',
},
// Send
send: {
back: '← Back',
selectToken: 'Select Token',
whichToken: 'Which token to send?',
someChainNotConnected: '⚠️ Some chains are not connected',
transferSuccess: 'Transfer Successful!',
wasSent: '{amount} {token} was sent',
done: 'Done',
sendToken: 'Send {token}',
changeToken: 'Change',
recipientAddress: 'Recipient Address',
scanQr: 'Scan QR',
scanQrText: 'Scan wallet address',
qrNotAvailable: 'QR scanner is not available on this platform',
invalidAddress: 'Invalid address',
amount: 'Amount',
balanceLabel: 'Balance:',
sending: 'Sending...',
sendButton: 'Send',
walletNotReady: 'Wallet is not ready',
selectTokenFirst: 'Select a token',
fillAddressAndAmount: 'Enter address and amount',
insufficientBalance: 'Insufficient balance',
mainnetApiNotReady: 'Mainnet API is not ready',
assetHubApiNotReady: 'Asset Hub API is not ready',
transferFailed: 'Transfer failed',
},
// Receive
receive: {
title: 'Receive',
back: '← Back',
qrFailed: 'QR generation failed',
shareAddress: 'This is your address. Share it to receive tokens.',
addressCopied: 'Copied!',
copyAddress: 'Copy Address',
},
// History
history: {
title: 'Transaction History',
back: '← Back',
loadingTx: 'Loading...',
noTransactions: 'No transactions',
historyAppears: 'Your history will appear here',
sent: 'Sent',
received: 'Received',
to: 'To:',
from: 'From:',
},
// Swap
swap: {
title: 'Token Swap',
fromLabel: 'From',
toLabel: 'To',
exchangeRate: 'Exchange Rate',
noPool: 'No pool',
swapping: 'Swapping...',
noPoolButton: 'No Pool',
swapButton: 'Swap',
swapFailed: 'Swap failed',
swapSuccess: 'Swap Successful!',
balanceLabel: 'Balance:',
insufficientBalance: 'Insufficient balance',
},
// Pools
pools: {
title: 'Liquidity Pools',
connectionError: 'Connection error - please retry',
loadingPools: 'Loading...',
noPools: 'No pools',
back: '← Back',
addLiquidity: 'Add Liquidity',
removeLiquidity: 'Remove Liquidity',
yourPosition: 'Your Position',
addButton: 'Add',
removeButton: 'Remove',
reserve: 'Reserve',
lpBalance: 'LP Balance:',
lpTokenAmount: 'LP Token Amount',
amountAuto: '{token} Amount (auto)',
adding: 'Adding...',
removing: 'Removing...',
addFailed: 'Add liquidity failed',
removeFailed: 'Remove liquidity failed',
invalidLpAmount: 'Invalid LP amount',
estimatedReturn: 'Estimated return:',
success: 'Success!',
addedLiquidity: '{amount0} {token0} + {amount1} {token1} added',
removedLiquidity: '{amount} LP tokens returned',
},
// Staking
staking: {
palletNotFound: 'Staking pallet not found',
fetchError: 'Failed to fetch staking info',
statusTab: 'Status',
bondTab: 'Bond',
nominateTab: 'Nominate',
unbondTab: 'Unbond',
activeStake: 'Active Stake',
totalBonded: 'Total Bonded',
nominations: 'Nominations',
rewardDestination: 'Reward Destination',
unbondingChunks: 'Unbonding',
nominatedValidators: 'Nominated Validators',
stakingTip: 'Staking HEZ increases Trust Score. Stake for at least 1 month for bonus.',
notStakedYet: 'Not Staked Yet',
stakeForTrustScore: 'Stake HEZ to earn Trust Score',
startStaking: 'Start Staking',
yourBalance: 'Your Balance',
currentlyStaked: 'Currently Staked',
amountHez: 'Amount (HEZ)',
bondWarning: 'Staking requires 28 days for withdrawal.',
bonding: 'Bonding...',
bondExtra: 'Add More',
bondButton: 'Bond',
bondFirst: 'First bond HEZ, then nominate',
selectValidators: 'Select validators (max 16): {count}/16',
commission: 'Commission:',
nominating: 'Nominating...',
nominateButton: 'Nominate',
notStakedUnbond: 'You have not staked yet',
unbondWarning: 'Unbonding takes 28 days. Then you can withdraw.',
unbondProcessing: 'Unbonding...',
unbondButton: 'Unbond',
bondSuccess: '{amount} HEZ staked successfully!',
nominateSuccess: '{count} validators nominated successfully!',
unbondSuccess: '{amount} HEZ unbonded! (28 days waiting)',
bondFailed: 'Bond failed',
nominateFailed: 'Nominate failed',
unbondFailed: 'Unbond failed',
},
// LP Staking
lpStaking: {
palletNotReady: 'Staking pallet is not ready',
poolsNotLoaded: 'Failed to load staking pools',
noPoolsYet: 'No staking pools yet',
selectPool: 'Select Pool',
totalStaked: 'Total Staked',
youStaked: 'You Staked',
lpBalance: 'LP Balance',
reward: 'Reward',
stakeTab: 'Stake',
unstakeTab: 'Unstake',
rewardTab: 'Rewards',
amount: 'Amount ({asset})',
balanceLabel: 'Balance:',
stakedLabel: 'Staked:',
staking: 'Staking...',
stakeButton: 'Stake',
unstaking: 'Unstaking...',
unstakeButton: 'Unstake',
pendingRewards: 'Pending Rewards',
claiming: 'Claiming...',
claimButton: 'Claim Rewards',
stakeFailed: 'Stake failed',
stakeSuccess: 'Staked successfully!',
unstakeFailed: 'Unstake failed',
unstakeSuccess: 'Unstaked successfully!',
claimFailed: 'Claim failed',
claimSuccess: 'Rewards claimed!',
},
// Fees
fees: {
title: 'Add Fee',
subtitle: 'HEZ teleport',
success: 'Success!',
sentTo: '{amount} HEZ sent to {chain}',
failed: 'Failed',
tryAgain: 'Try Again',
targetChain: 'Target Chain',
forTransfers: 'For PEZ transfers',
forIdentity: 'For identity',
minRecommended: '{description} minimum 0.1 HEZ recommended.',
amountHez: 'Amount (HEZ)',
signing: 'Sign the transaction...',
xcmTeleportPending: 'XCM Teleport in progress...',
signingButton: 'Signing...',
processing: 'Processing...',
sendTo: 'Send to {chain}',
walletNotConnected: 'Wallet not connected',
apiNotConnected: 'API not connected',
enterValidAmount: 'Enter a valid amount',
chainNotConnected: 'Chain not connected',
insufficientBalance: 'Insufficient balance',
xcmPalletNotFound: 'XCM pallet not found',
teleportFailed: 'Teleport failed',
errorOccurred: 'An error occurred',
},
// Tokens
tokens: {
searchPlaceholder: 'Search token...',
addToken: 'Add Token',
assetIdPlaceholder: 'Enter Asset ID (e.g. 3)',
cancel: 'Cancel',
add: 'Add',
blockchainConnected: 'Pezkuwichain Connected',
connectingBlockchain: 'Connecting to Blockchain...',
connectingRpc: 'Connecting to RPC server...',
tokenNotFound: 'Token not found',
total: 'Total',
loadingBalance: 'Loading...',
},
// Errors
errors: {
networkError: 'No internet connection. Please check your connection.',
timeout: 'Operation took too long. Please try again.',
walletNotFound: 'Wallet not found. Please create or restore a wallet.',
wrongPassword: 'Wrong password. Please try again.',
default: 'Something went wrong. Please try again.',
},
// Validation
validation: {
minLength: 'Password must be at least 12 characters',
needLowercase: 'Password must contain at least 1 lowercase letter (a-z)',
needUppercase: 'Password must contain at least 1 uppercase letter (A-Z)',
needNumber: 'Password must contain at least 1 number (0-9)',
needSpecialChar: 'Password must contain at least 1 special character (!@#$%...)',
weakPassword: 'Password is not strong enough. Try a longer password with diverse characters.',
},
// Time
time: {
now: 'Now',
minutesAgo: '{count} min ago',
hoursAgo: '{count} hours ago',
daysAgo: '{count} days ago',
},
// Context
context: {
walletInitFailed: 'Wallet initialization failed',
rpcDisconnected: 'RPC connection lost. Reconnecting...',
pleaseLoginFirst: 'Please log in first',
invalidSeedPhrase: 'Invalid seed phrase',
connectionFailed: 'Connection failed',
referralStatsError: 'Failed to load referral stats',
referralApproved: 'Referral approved! Your count: {count}',
wrongPasswordError: 'Wrong password',
walletSyncFailed: 'Wallet address sync to DB failed',
},
};
export default en;
+275
View File
@@ -300,6 +300,281 @@ const fa: Translations = {
loadingScreen: {
loading: 'در حال بارگذاری...',
},
// Dashboard
dashboard: {
connected: 'متصل',
send: 'ارسال',
receive: 'دریافت',
presaleMessage: 'پیش‌فروش در راه است! به زودی باز می‌شود.',
depositUsdt: 'افزودن USDT',
depositUsdtDesc: 'از شبکه‌های TON، Polkadot یا TRC20',
recentActivity: 'فعالیت‌های اخیر',
history: 'تاریخچه',
refreshTx: 'بروزرسانی',
loadingTx: 'در حال بارگذاری...',
noRecentTx: 'تراکنش اخیری نیست',
historyAppears: 'تاریخچه شما اینجا نمایش داده می‌شود',
sent: 'ارسال شد',
received: 'دریافت شد',
selectStaking: 'انتخاب Staking',
validatorNominate: 'نامزد کردن اعتبارسنج',
trustScorePlus: 'Trust Score +',
lpStakeDesc: 'استیک کردن LP token',
pezRewardPlus: 'پاداش PEZ +',
goBack: 'بازگشت',
},
// Send
send: {
back: '← بازگشت',
selectToken: 'انتخاب Token',
whichToken: 'کدام token را ارسال کنید؟',
someChainNotConnected: '⚠️ برخی زنجیره‌ها متصل نیستند',
transferSuccess: 'انتقال موفق!',
wasSent: '{amount} {token} ارسال شد',
done: 'تمام',
sendToken: 'ارسال {token}',
changeToken: 'تغییر',
recipientAddress: 'آدرس گیرنده',
scanQr: 'اسکن QR',
scanQrText: 'آدرس کیف پول را اسکن کنید',
qrNotAvailable: 'اسکنر QR در این پلتفرم موجود نیست',
invalidAddress: 'آدرس نامعتبر',
amount: 'مقدار',
balanceLabel: 'موجودی:',
sending: 'در حال ارسال...',
sendButton: 'ارسال',
walletNotReady: 'کیف پول آماده نیست',
selectTokenFirst: 'Token انتخاب کنید',
fillAddressAndAmount: 'آدرس و مقدار را وارد کنید',
insufficientBalance: 'موجودی کافی نیست',
mainnetApiNotReady: 'Mainnet API آماده نیست',
assetHubApiNotReady: 'Asset Hub API آماده نیست',
transferFailed: 'انتقال ناموفق',
},
// Receive
receive: {
title: 'دریافت',
back: '← بازگشت',
qrFailed: 'ساخت QR ناموفق',
shareAddress: 'این آدرس شماست. برای دریافت token این آدرس را به اشتراک بگذارید.',
addressCopied: 'کپی شد!',
copyAddress: 'کپی آدرس',
},
// History
history: {
title: 'تاریخچه تراکنش‌ها',
back: '← بازگشت',
loadingTx: 'در حال بارگذاری...',
noTransactions: 'تراکنشی نیست',
historyAppears: 'تاریخچه شما اینجا نمایش داده می‌شود',
sent: 'ارسال شد',
received: 'دریافت شد',
to: 'به:',
from: 'از:',
},
// Swap
swap: {
title: 'Token Swap',
fromLabel: 'از',
toLabel: 'به',
exchangeRate: 'نرخ تبدیل',
noPool: 'Pool نیست',
swapping: 'در حال تبدیل...',
noPoolButton: 'Pool نیست',
swapButton: 'Swap',
swapFailed: 'Swap ناموفق',
swapSuccess: 'Swap موفق!',
balanceLabel: 'موجودی:',
insufficientBalance: 'موجودی کافی نیست',
},
// Pools
pools: {
title: 'Liquidity Pools',
connectionError: 'خطای اتصال - دوباره تلاش کنید',
loadingPools: 'در حال بارگذاری...',
noPools: 'Pool نیست',
back: '← بازگشت',
addLiquidity: 'افزودن Liquidity',
removeLiquidity: 'برداشت Liquidity',
yourPosition: 'موقعیت شما',
addButton: 'افزودن',
removeButton: 'برداشت',
reserve: 'ذخیره',
lpBalance: 'LP موجودی:',
lpTokenAmount: 'مقدار LP Token',
amountAuto: '{token} مقدار (خودکار)',
adding: 'در حال افزودن...',
removing: 'در حال برداشت...',
addFailed: 'افزودن ناموفق',
removeFailed: 'برداشت ناموفق',
invalidLpAmount: 'مقدار LP نامعتبر',
estimatedReturn: 'بازگشت تخمینی:',
success: 'موفق!',
addedLiquidity: '{amount0} {token0} + {amount1} {token1} اضافه شد',
removedLiquidity: '{amount} LP token بازگردانده شد',
},
// Staking
staking: {
palletNotFound: 'ماژول Staking یافت نشد',
fetchError: 'اطلاعات staking دریافت نشد',
statusTab: 'وضعیت',
bondTab: 'Bond',
nominateTab: 'نامزد',
unbondTab: 'Unbond',
activeStake: 'Stake فعال',
totalBonded: 'کل Bonded',
nominations: 'نامزدها',
rewardDestination: 'مقصد پاداش',
unbondingChunks: 'Unbonding',
nominatedValidators: 'اعتبارسنج‌های نامزد شده',
stakingTip: 'استیک HEZ امتیاز اعتماد را افزایش می‌دهد. حداقل ۱ ماه استیک کنید.',
notStakedYet: 'هنوز استیک نشده',
stakeForTrustScore: 'HEZ استیک کنید برای کسب Trust Score',
startStaking: 'شروع',
yourBalance: 'موجودی شما',
currentlyStaked: 'فعلاً Staked',
amountHez: 'مقدار (HEZ)',
bondWarning: 'استیک کردن ۲۸ روز زمان برداشت دارد.',
bonding: 'Bond در حال انجام...',
bondExtra: 'افزودن بیشتر',
bondButton: 'Bond',
bondFirst: 'ابتدا HEZ bond کنید، سپس نامزد کنید',
selectValidators: 'اعتبارسنج انتخاب کنید (حداکثر ۱۶): {count}/16',
commission: 'کمیسیون:',
nominating: 'نامزد کردن...',
nominateButton: 'نامزد کن',
notStakedUnbond: 'هنوز استیک نکرده‌اید',
unbondWarning: 'Unbond کردن ۲۸ روز طول می‌کشد. بعداً می‌توانید برداشت کنید.',
unbondProcessing: 'Unbond در حال انجام...',
unbondButton: 'Unbond',
bondSuccess: '{amount} HEZ با موفقیت استیک شد!',
nominateSuccess: '{count} اعتبارسنج با موفقیت نامزد شدند!',
unbondSuccess: '{amount} HEZ unbond شد! (۲۸ روز انتظار)',
bondFailed: 'Bond ناموفق',
nominateFailed: 'نامزدی ناموفق',
unbondFailed: 'Unbond ناموفق',
},
// LP Staking
lpStaking: {
palletNotReady: 'ماژول Staking آماده نیست',
poolsNotLoaded: 'Pool های staking بارگذاری نشدند',
noPoolsYet: 'هنوز pool staking نیست',
selectPool: 'Pool انتخاب کنید',
totalStaked: 'کل Staked',
youStaked: 'شما Stake کردید',
lpBalance: 'LP موجودی',
reward: 'پاداش',
stakeTab: 'Stake',
unstakeTab: 'Unstake',
rewardTab: 'پاداش',
amount: 'مقدار ({asset})',
balanceLabel: 'موجودی:',
stakedLabel: 'Staked:',
staking: 'Stake در حال انجام...',
stakeButton: 'Stake',
unstaking: 'Unstake در حال انجام...',
unstakeButton: 'Unstake',
pendingRewards: 'پاداش‌های در انتظار',
claiming: 'دریافت...',
claimButton: 'دریافت پاداش',
stakeFailed: 'Stake ناموفق',
stakeSuccess: 'Stake موفق!',
unstakeFailed: 'Unstake ناموفق',
unstakeSuccess: 'Unstake موفق!',
claimFailed: 'دریافت پاداش ناموفق',
claimSuccess: 'پاداش دریافت شد!',
},
// Fees
fees: {
title: 'افزودن Fee',
subtitle: 'HEZ teleport',
success: 'موفق!',
sentTo: '{amount} HEZ به {chain} ارسال شد',
failed: 'ناموفق',
tryAgain: 'دوباره تلاش کنید',
targetChain: 'زنجیره هدف',
forTransfers: 'برای انتقال PEZ',
forIdentity: 'برای هویت',
minRecommended: '{description} حداقل ۰.۱ HEZ توصیه می‌شود.',
amountHez: 'مقدار (HEZ)',
signing: 'امضای تراکنش...',
xcmTeleportPending: 'XCM Teleport در حال انجام...',
signingButton: 'در حال امضا...',
processing: 'در حال پردازش...',
sendTo: 'ارسال به {chain}',
walletNotConnected: 'کیف پول متصل نیست',
apiNotConnected: 'API متصل نیست',
enterValidAmount: 'مقدار معتبر وارد کنید',
chainNotConnected: 'زنجیره متصل نیست',
insufficientBalance: 'موجودی کافی نیست',
xcmPalletNotFound: 'ماژول XCM یافت نشد',
teleportFailed: 'Teleport ناموفق',
errorOccurred: 'خطایی رخ داد',
},
// Tokens
tokens: {
searchPlaceholder: 'جستجوی token...',
addToken: 'افزودن Token',
assetIdPlaceholder: 'Asset ID وارد کنید (مثال: 3)',
cancel: 'لغو',
add: 'افزودن',
blockchainConnected: 'Pezkuwichain متصل',
connectingBlockchain: 'اتصال به Blockchain...',
connectingRpc: 'اتصال به سرور RPC...',
tokenNotFound: 'Token یافت نشد',
total: 'کل',
loadingBalance: 'در حال بارگذاری...',
},
// Errors
errors: {
networkError: 'اتصال اینترنت نیست. لطفاً اتصال خود را بررسی کنید.',
timeout: 'عملیات بیش از حد طول کشید. لطفاً دوباره تلاش کنید.',
walletNotFound: 'کیف پول یافت نشد. لطفاً کیف پول بسازید یا بازیابی کنید.',
wrongPassword: 'رمز عبور اشتباه. لطفاً دوباره تلاش کنید.',
default: 'مشکلی پیش آمد. لطفاً دوباره تلاش کنید.',
},
// Validation
validation: {
minLength: 'رمز عبور حداقل ۱۲ کاراکتر باشد',
needLowercase: 'رمز عبور حداقل ۱ حرف کوچک داشته باشد (a-z)',
needUppercase: 'رمز عبور حداقل ۱ حرف بزرگ داشته باشد (A-Z)',
needNumber: 'رمز عبور حداقل ۱ عدد داشته باشد (0-9)',
needSpecialChar: 'رمز عبور حداقل ۱ کاراکتر خاص داشته باشد (!@#$%...)',
weakPassword: 'رمز عبور کافی قوی نیست. رمز عبوری طولانی‌تر با کاراکترهای متنوع امتحان کنید.',
},
// Time
time: {
now: 'الان',
minutesAgo: '{count} دقیقه پیش',
hoursAgo: '{count} ساعت پیش',
daysAgo: '{count} روز پیش',
},
// Context
context: {
walletInitFailed: 'راه‌اندازی کیف پول ناموفق',
rpcDisconnected: 'اتصال RPC قطع شد. در حال اتصال مجدد...',
pleaseLoginFirst: 'لطفاً ابتدا وارد شوید',
invalidSeedPhrase: 'Seed phrase نامعتبر',
connectionFailed: 'اتصال ناموفق',
referralStatsError: 'آمار referral بارگذاری نشد',
referralApproved: 'Referral تأیید شد! تعداد شما: {count}',
wrongPasswordError: 'رمز عبور اشتباه',
walletSyncFailed: 'همگام‌سازی آدرس کیف پول با DB ناموفق',
},
};
export default fa;
+285
View File
@@ -315,6 +315,291 @@ const krd: Translations = {
loadingScreen: {
loading: 'T\u00ea barkirin...',
},
// Dashboard
dashboard: {
connected: 'Gir\u00eaday\u00ee',
send: 'Bi\u015f\u00eene',
receive: 'Werbigire',
presaleMessage:
'Presale t\u00ea de ye! Z\u00fbtir\u00een demek\u00ea de d\u00ea b\u00eate vekirin.',
depositUsdt: 'USDT Z\u00eade Bike',
depositUsdtDesc: 'TON, Polkadot an TRC20 ji zinc\u00eer\u00ean din',
recentActivity: '\u00c7alakiya Daw\u00ee',
history: 'D\u00eerok',
refreshTx: 'N\u00fbkirin',
loadingTx: 'T\u00ea barkirin...',
noRecentTx: 'Transaksiyona daw\u00ee tune',
historyAppears: 'D\u00eeroka te d\u00ea li vir xuya bibe',
sent: '\u015eandin',
received: 'Wergirtin',
selectStaking: 'Staking Hilbij\u00eare',
validatorNominate: 'Validator nominate bike',
trustScorePlus: 'Trust Score +',
lpStakeDesc: 'LP token stake bike',
pezRewardPlus: 'PEZ Xelat +',
goBack: 'Pa\u015fve',
},
// Send
send: {
back: '\u2190 Pa\u015f',
selectToken: 'Token Hilbij\u00eare',
whichToken: 'K\u00eejan token bi\u015f\u00eene?',
someChainNotConnected: '\u26a0\ufe0f Hinek chain gir\u00eaday\u00ee n\u00eene',
transferSuccess: 'Transfer Serket\u00ee!',
wasSent: '{amount} {token} hat \u015fandin',
done: 'Temam',
sendToken: 'Bi\u015f\u00eene {token}',
changeToken: 'Biguhere',
recipientAddress: 'Navn\u00ee\u015fana Wergir',
scanQr: 'QR Bi\u015fox\u00eene',
scanQrText: 'Navn\u00ee\u015fana c\u00eezdan\u00ea bi\u015fox\u00eene',
qrNotAvailable: 'QR okuyucu v\u00ea platform\u00ea de amade n\u00eene',
invalidAddress: 'Navn\u00ee\u015fana derbasdar n\u00eene',
amount: 'M\u00eeqdar',
balanceLabel: 'Bakiye:',
sending: 'T\u00ea \u015fandin...',
sendButton: 'Bi\u015f\u00eene',
walletNotReady: 'C\u00eezdan amade n\u00eene',
selectTokenFirst: 'Token hilbij\u00eare',
fillAddressAndAmount: 'Navn\u00ee\u015fan \u00fb m\u00eeqdar biniv\u00eese',
insufficientBalance: 'Bakiye t\u00ear\u00ea nake',
mainnetApiNotReady: 'Mainnet API amade n\u00eene',
assetHubApiNotReady: 'Asset Hub API amade n\u00eene',
transferFailed: 'Transfer neserket\u00ee',
},
// Receive
receive: {
title: 'Werbigire',
back: '\u2190 Pa\u015f',
qrFailed: 'QR \u00e7\u00eaneb\u00fb',
shareAddress:
'Ev navn\u00ee\u015fana te ye. Ji bo wergirtina token v\u00ea navn\u00ee\u015fan\u00ea parve bike.',
addressCopied: 'Hat kop\u00eekirin!',
copyAddress: 'Navn\u00ee\u015fan\u00ea Kop\u00ee Bike',
},
// History
history: {
title: 'D\u00eeroka Transaksiyonan',
back: '\u2190 Pa\u015f',
loadingTx: 'T\u00ea barkirin...',
noTransactions: 'Transaksiyona tune',
historyAppears: 'D\u00eeroka te d\u00ea li vir xuya bibe',
sent: '\u015eandin',
received: 'Wergirtin',
to: 'Ji bo:',
from: 'Ji:',
},
// Swap
swap: {
title: 'Token Swap',
fromLabel: 'Ji (From)',
toLabel: 'Bo (To)',
exchangeRate: 'R\u00eajeya Guherandin\u00ea',
noPool: 'Pool tune',
swapping: 'T\u00ea guhertin...',
noPoolButton: 'Pool Tune',
swapButton: 'Swap Bike',
swapFailed: 'Swap neserket\u00ee',
swapSuccess: 'Swap Serket\u00ee!',
balanceLabel: 'Bakiye:',
insufficientBalance: 'Bakiye t\u00ear\u00ea nake',
},
// Pools
pools: {
title: 'Liquidity Pools',
connectionError: 'Gir\u00eadan\u00ea \u00e7ewt\u00ee - d\u00eesa bicerb\u00eene',
loadingPools: 'T\u00ea barkirin...',
noPools: 'Pool tune',
back: '\u2190 Pa\u015f',
addLiquidity: 'Liquidity Z\u00eade Bike',
removeLiquidity: 'Liquidity Derxe',
yourPosition: 'Poz\u00eesyona Te',
addButton: 'Z\u00eade Bike',
removeButton: 'Derxe',
reserve: 'Rezerv',
lpBalance: 'LP Bakiye:',
lpTokenAmount: 'LP Token M\u00eeqdar',
amountAuto: '{token} M\u00eeqdar (otomat\u00eek)',
adding: 'T\u00ea z\u00eadekirin...',
removing: 'T\u00ea derxistin...',
addFailed: 'Z\u00eadekirin neserket\u00ee',
removeFailed: 'Derxistin neserket\u00ee',
invalidLpAmount: 'M\u00eeqdara LP ne derbasdar e',
estimatedReturn: 'Texm\u00een\u00ee vegerandin:',
success: 'Serket\u00ee!',
addedLiquidity: '{amount0} {token0} + {amount1} {token1} hate z\u00eadekirin',
removedLiquidity: '{amount} LP token hate vegerandin',
},
// Staking
staking: {
palletNotFound: 'Staking pallet nehate d\u00eetin',
fetchError: 'Staking agah\u00ee nehate stendin',
statusTab: 'Durum',
bondTab: 'Bond',
nominateTab: 'Nominate',
unbondTab: 'Unbond',
activeStake: 'Akt\u00eef Stake',
totalBonded: 'Gi\u015ft\u00ee Bonded',
nominations: 'Nominations',
rewardDestination: 'Xelat Armanc',
unbondingChunks: 'Unbonding',
nominatedValidators: 'Nominated Validators',
stakingTip:
'HEZ stake kirin Trust Score z\u00eade dike. Her\u00ee k\u00eam 1 meh stake bik\u00ee ji bo bonus\u00ea.',
notStakedYet: 'H\u00een Stake Nekiriye',
stakeForTrustScore: 'HEZ stake bike ji bo Trust Score qezenckirin',
startStaking: 'Dest P\u00ea Bike',
yourBalance: 'Bakiy\u00ea Te',
currentlyStaked: 'Niha Staked',
amountHez: 'M\u00eeqdar (HEZ)',
bondWarning:
'Stake kirin\u00ea pa\u015f\u00ea 28 roj li bend\u00ea ye ji bo veki\u015fandin\u00ea.',
bonding: 'T\u00ea bond kirin...',
bondExtra: 'Z\u00eade Bike',
bondButton: 'Bond Bike',
bondFirst: 'P\u00ea\u015f\u00ee HEZ bond bike, pa\u015f\u00ea nominate bike',
selectValidators: 'Validator hilbij\u00eare (max 16): {count}/16',
commission: 'Kom\u00eesyon:',
nominating: 'T\u00ea nominate kirin...',
nominateButton: 'Nominate Bike',
notStakedUnbond: 'Tu h\u00een stake nekiriye',
unbondWarning: 'Unbond kirin 28 roj digire. Pa\u015f\u00ea dikare veki\u015f\u00eene.',
unbondProcessing: 'T\u00ea unbond kirin...',
unbondButton: 'Unbond Bike',
bondSuccess: '{amount} HEZ stake kirin serket\u00ee!',
nominateSuccess: '{count} validator nominate kirin serket\u00ee!',
unbondSuccess: '{amount} HEZ unbond kirin serket\u00ee! (28 roj li bend\u00ea)',
bondFailed: 'Bond neserket\u00ee',
nominateFailed: 'Nominate neserket\u00ee',
unbondFailed: 'Unbond neserket\u00ee',
},
// LP Staking
lpStaking: {
palletNotReady: 'Staking palleti amade n\u00eene',
poolsNotLoaded: 'Staking pools bar nekirin',
noPoolsYet: 'H\u00eaj staking pool tune ne',
selectPool: 'Pool Hilbij\u00eare',
totalStaked: 'Gi\u015ft\u00ee Staked',
youStaked: 'Te Stake Kiriye',
lpBalance: 'LP Bakiye',
reward: 'Xelat',
stakeTab: 'Stake',
unstakeTab: 'Unstake',
rewardTab: 'Xelat',
amount: 'M\u00eeqdar ({asset})',
balanceLabel: 'Bakiye:',
stakedLabel: 'Staked:',
staking: 'T\u00ea stake kirin...',
stakeButton: 'Stake Bike',
unstaking: 'T\u00ea unstake kirin...',
unstakeButton: 'Unstake Bike',
pendingRewards: 'Xelat\u00ean li bend\u00ea',
claiming: 'T\u00ea stendin...',
claimButton: 'Xelatan Bist\u00eene',
stakeFailed: 'Stake neserket\u00ee',
stakeSuccess: 'Stake serket!',
unstakeFailed: 'Unstake neserket\u00ee',
unstakeSuccess: 'Unstake serket!',
claimFailed: 'Xelat stendin neserket\u00ee',
claimSuccess: 'Xelat hat stendin!',
},
// Fees
fees: {
title: 'Fee Z\u00eade Bike',
subtitle: 'HEZ teleport',
success: 'Serket\u00ee!',
sentTo: '{amount} HEZ bo {chain} hate \u015fandin',
failed: 'Neserket\u00ee',
tryAgain: 'D\u00eesa Bicerb\u00eene',
targetChain: 'Zinc\u00eera Armanc',
forTransfers: 'Ji bo PEZ veguheztin',
forIdentity: 'Ji bo nasname',
minRecommended: '{description} k\u00eam\u00ee 0.1 HEZ t\u00ea p\u00ea\u015fniyarkirin.',
amountHez: 'M\u00eeqdar (HEZ)',
signing: 'Dan\u00fbstandin\u00ea \u00eemze bikin...',
xcmTeleportPending: 'XCM Teleport t\u00ea \u00e7\u00eakirin...',
signingButton: 'T\u00ea \u00eemzekirin...',
processing: 'T\u00ea \u00e7\u00eakirin...',
sendTo: 'Bo {chain} Bi\u015f\u00eene',
walletNotConnected: 'Cizdan gir\u00eaday\u00ee n\u00eene',
apiNotConnected: 'API gir\u00eaday\u00ee n\u00eene',
enterValidAmount: 'M\u00eeqdarek rast biniv\u00eese',
chainNotConnected: 'Zinc\u00eer gir\u00eaday\u00ee n\u00eene',
insufficientBalance: 'Bakiye t\u00ear\u00ea nake',
xcmPalletNotFound: 'XCM pallet nehate d\u00eetin',
teleportFailed: 'Teleport neserket\u00ee',
errorOccurred: '\u00c7ewtiy\u00eak\u00ee \u00e7\u00eab\u00fb',
},
// Tokens
tokens: {
searchPlaceholder: 'Token bigere...',
addToken: 'Token Z\u00eade Bike',
assetIdPlaceholder: 'Asset ID biniv\u00eese (m\u00eenak: 3)',
cancel: 'Betal',
add: 'Z\u00eade Bike',
blockchainConnected: 'Pezkuwichain Gir\u00eaday\u00ee',
connectingBlockchain: 'Gir\u00eadana Blockchain...',
connectingRpc: 'RPC server\u00ea t\u00ea gir\u00eadan...',
tokenNotFound: 'Token nehat d\u00eetin',
total: 'Gi\u015ft\u00ee',
loadingBalance: 'T\u00ea barkirin...',
},
// Errors
errors: {
networkError:
'T\u00eakiliya \u00eenternet\u00ea tune ye. Ji kerema xwe t\u00eakiliya xwe kontrol bike.',
timeout: 'Operasyon z\u00eade dir\u00eaj ki\u015fand. Ji kerema xwe d\u00eesa bicerb\u00eene.',
walletNotFound:
'Wallet nehate d\u00eetin. Ji kerema xwe wallet \u00e7\u00eake an j\u00ee restore bike.',
wrongPassword:
'\u015e\u00eefre (password) \u00e7ewt e. Ji kerema xwe d\u00eesa bicerb\u00eene.',
default: 'Ti\u015ftek \u00e7ewt \u00e7\u00eab\u00fb. Ji kerema xwe d\u00eesa bicerb\u00eene.',
},
// Validation
validation: {
minLength: '\u015e\u00eefre (password) her\u00ee k\u00eam 12 t\u00eep be',
needLowercase:
'\u015e\u00eefre (password) her\u00ee k\u00eam 1 t\u00eepa bi\u00e7\u00fbk hebe (a-z)',
needUppercase: '\u015e\u00eefre (password) her\u00ee k\u00eam 1 t\u00eepa mezin hebe (A-Z)',
needNumber: '\u015e\u00eefre (password) her\u00ee k\u00eam 1 hejmar hebe (0-9)',
needSpecialChar:
'\u015e\u00eefre (password) her\u00ee k\u00eam 1 n\u00ee\u015fana taybet hebe (!@#$%...)',
weakPassword:
'\u015e\u00eefre (password) ne t\u00eara qew\u00ee ye. \u015e\u00eefreyek (password) dir\u00eajtir bi t\u00eep\u00ean c\u00fbrbecu\u0308r bicerb\u00eene.',
},
// Time
time: {
now: 'Niha',
minutesAgo: '{count} deq ber\u00ea',
hoursAgo: '{count} saet ber\u00ea',
daysAgo: '{count} roj ber\u00ea',
},
// Context
context: {
walletInitFailed: 'Wallet dest p\u00ea nekir',
rpcDisconnected: 'T\u00eakiliya RPC qut b\u00fb. D\u00eesa gir\u00eadan t\u00ea kirin...',
pleaseLoginFirst: 'Ji kerema xwe p\u00ea\u015f\u00ee t\u00eakeve',
invalidSeedPhrase: 'Seed phrase ne derbasdar e',
connectionFailed: 'Gir\u00eadan neserket\u00ee',
referralStatsError: 'Referral stats bar nekirin',
referralApproved: 'Referral hat pejirand\u00een! Hejmara te: {count}',
wrongPasswordError: '\u015e\u00eefre (password) \u00e7ewt e',
walletSyncFailed: 'Wallet adresa DB-\u00ea re senkron\u00eeze neb\u00fb',
},
};
export default krd;
+276
View File
@@ -300,6 +300,282 @@ const tr: Translations = {
loadingScreen: {
loading: 'Yükleniyor...',
},
// Dashboard
dashboard: {
connected: 'Bağlı',
send: 'Gönder',
receive: 'Al',
presaleMessage: 'Presale yakında! Çok yakında açılacak.',
depositUsdt: 'USDT Ekle',
depositUsdtDesc: 'TON, Polkadot veya TRC20 ağlarından',
recentActivity: 'Son İşlemler',
history: 'Geçmiş',
refreshTx: 'Yenile',
loadingTx: 'Yükleniyor...',
noRecentTx: 'Son işlem yok',
historyAppears: 'Geçmişiniz burada görünecek',
sent: 'Gönderildi',
received: 'Alındı',
selectStaking: 'Staking Seç',
validatorNominate: 'Validator aday göster',
trustScorePlus: 'Trust Score +',
lpStakeDesc: 'LP token stake et',
pezRewardPlus: 'PEZ Ödül +',
goBack: 'Geri',
},
// Send
send: {
back: '← Geri',
selectToken: 'Token Seç',
whichToken: 'Hangi tokeni göndermek istiyorsun?',
someChainNotConnected: '⚠️ Bazı zincirler bağlı değil',
transferSuccess: 'Transfer Başarılı!',
wasSent: '{amount} {token} gönderildi',
done: 'Tamam',
sendToken: 'Gönder {token}',
changeToken: 'Değiştir',
recipientAddress: 'Alıcı Adresi',
scanQr: 'QR Tara',
scanQrText: 'Cüzdan adresini tara',
qrNotAvailable: 'QR okuyucu bu platformda mevcut değil',
invalidAddress: 'Geçersiz adres',
amount: 'Miktar',
balanceLabel: 'Bakiye:',
sending: 'Gönderiliyor...',
sendButton: 'Gönder',
walletNotReady: 'Cüzdan hazır değil',
selectTokenFirst: 'Token seçin',
fillAddressAndAmount: 'Adres ve miktar girin',
insufficientBalance: 'Yetersiz bakiye',
mainnetApiNotReady: 'Mainnet API hazır değil',
assetHubApiNotReady: 'Asset Hub API hazır değil',
transferFailed: 'Transfer başarısız',
},
// Receive
receive: {
title: 'Al',
back: '← Geri',
qrFailed: 'QR oluşturulamadı',
shareAddress: 'Bu sizin adresiniz. Token almak için bu adresi paylaşın.',
addressCopied: 'Kopyalandı!',
copyAddress: 'Adresi Kopyala',
},
// History
history: {
title: 'İşlem Geçmişi',
back: '← Geri',
loadingTx: 'Yükleniyor...',
noTransactions: 'İşlem yok',
historyAppears: 'Geçmişiniz burada görünecek',
sent: 'Gönderildi',
received: 'Alındı',
to: 'Kime:',
from: 'Kimden:',
},
// Swap
swap: {
title: 'Token Swap',
fromLabel: 'Kimden',
toLabel: 'Kime',
exchangeRate: 'Döviz Kuru',
noPool: 'Pool yok',
swapping: 'Takas ediliyor...',
noPoolButton: 'Pool Yok',
swapButton: 'Swap Yap',
swapFailed: 'Swap başarısız',
swapSuccess: 'Swap Başarılı!',
balanceLabel: 'Bakiye:',
insufficientBalance: 'Yetersiz bakiye',
},
// Pools
pools: {
title: 'Likidite Havuzları',
connectionError: 'Bağlantı hatası - tekrar deneyin',
loadingPools: 'Yükleniyor...',
noPools: 'Pool yok',
back: '← Geri',
addLiquidity: 'Likidite Ekle',
removeLiquidity: 'Likidite Çıkar',
yourPosition: 'Pozisyonunuz',
addButton: 'Ekle',
removeButton: 'Çıkar',
reserve: 'Rezerv',
lpBalance: 'LP Bakiye:',
lpTokenAmount: 'LP Token Miktarı',
amountAuto: '{token} Miktar (otomatik)',
adding: 'Ekleniyor...',
removing: 'Çıkarılıyor...',
addFailed: 'Ekleme başarısız',
removeFailed: 'Çıkarma başarısız',
invalidLpAmount: 'Geçersiz LP miktarı',
estimatedReturn: 'Tahmini geri dönüş:',
success: 'Başarılı!',
addedLiquidity: '{amount0} {token0} + {amount1} {token1} eklendi',
removedLiquidity: '{amount} LP token geri alındı',
},
// Staking
staking: {
palletNotFound: 'Staking modülü bulunamadı',
fetchError: 'Staking bilgileri alınamadı',
statusTab: 'Durum',
bondTab: 'Bond',
nominateTab: 'Aday Göster',
unbondTab: 'Unbond',
activeStake: 'Aktif Stake',
totalBonded: 'Toplam Bonded',
nominations: 'Adaylıklar',
rewardDestination: 'Ödül Hedefi',
unbondingChunks: 'Unbonding',
nominatedValidators: 'Aday Gösterilen Validatörler',
stakingTip: "HEZ stake etmek Trust Score'u artırır. Bonus için en az 1 ay stake edin.",
notStakedYet: 'Henüz Stake Yok',
stakeForTrustScore: 'Trust Score kazanmak için HEZ stake edin',
startStaking: 'Başla',
yourBalance: 'Bakiyeniz',
currentlyStaked: 'Şu an Staked',
amountHez: 'Miktar (HEZ)',
bondWarning: 'Stake etme sonrası çekim için 28 gün bekleme süresi vardır.',
bonding: 'Bond ediliyor...',
bondExtra: 'Daha Ekle',
bondButton: 'Bond Et',
bondFirst: 'Önce HEZ bond edin, sonra aday gösterin',
selectValidators: 'Validatör seçin (max 16): {count}/16',
commission: 'Komisyon:',
nominating: 'Aday gösteriliyor...',
nominateButton: 'Aday Göster',
notStakedUnbond: 'Henüz stake yapmadınız',
unbondWarning: 'Unbond işlemi 28 gün sürer. Sonra çekilebilir.',
unbondProcessing: 'Unbond ediliyor...',
unbondButton: 'Unbond Et',
bondSuccess: '{amount} HEZ başarıyla stake edildi!',
nominateSuccess: '{count} validatör başarıyla aday gösterildi!',
unbondSuccess: '{amount} HEZ unbond edildi! (28 gün bekleme)',
bondFailed: 'Bond başarısız',
nominateFailed: 'Aday gösterme başarısız',
unbondFailed: 'Unbond başarısız',
},
// LP Staking
lpStaking: {
palletNotReady: 'Staking modülü hazır değil',
poolsNotLoaded: 'Staking havuzları yüklenemedi',
noPoolsYet: 'Henüz staking havuzu yok',
selectPool: 'Pool Seç',
totalStaked: 'Toplam Staked',
youStaked: 'Stake Ettiniz',
lpBalance: 'LP Bakiye',
reward: 'Ödül',
stakeTab: 'Stake',
unstakeTab: 'Unstake',
rewardTab: 'Ödül',
amount: 'Miktar ({asset})',
balanceLabel: 'Bakiye:',
stakedLabel: 'Staked:',
staking: 'Stake ediliyor...',
stakeButton: 'Stake Et',
unstaking: 'Unstake ediliyor...',
unstakeButton: 'Unstake Et',
pendingRewards: 'Bekleyen Ödüller',
claiming: 'Alınıyor...',
claimButton: 'Ödülleri Al',
stakeFailed: 'Stake başarısız',
stakeSuccess: 'Stake başarılı!',
unstakeFailed: 'Unstake başarısız',
unstakeSuccess: 'Unstake başarılı!',
claimFailed: 'Ödül alma başarısız',
claimSuccess: 'Ödüller alındı!',
},
// Fees
fees: {
title: 'Fee Ekle',
subtitle: 'HEZ teleport',
success: 'Başarılı!',
sentTo: '{amount} HEZ {chain} ağına gönderildi',
failed: 'Başarısız',
tryAgain: 'Tekrar Dene',
targetChain: 'Hedef Zincir',
forTransfers: 'PEZ transferi için',
forIdentity: 'Kimlik için',
minRecommended: '{description} minimum 0.1 HEZ önerilir.',
amountHez: 'Miktar (HEZ)',
signing: 'İşlem imzalanıyor...',
xcmTeleportPending: 'XCM Teleport devam ediyor...',
signingButton: 'İmzalanıyor...',
processing: 'İşleniyor...',
sendTo: '{chain} ağına Gönder',
walletNotConnected: 'Cüzdan bağlı değil',
apiNotConnected: 'API bağlı değil',
enterValidAmount: 'Geçerli bir miktar girin',
chainNotConnected: 'Zincir bağlı değil',
insufficientBalance: 'Yetersiz bakiye',
xcmPalletNotFound: 'XCM modülü bulunamadı',
teleportFailed: 'Teleport başarısız',
errorOccurred: 'Bir hata oluştu',
},
// Tokens
tokens: {
searchPlaceholder: 'Token ara...',
addToken: 'Token Ekle',
assetIdPlaceholder: 'Asset ID girin (ör: 3)',
cancel: 'İptal',
add: 'Ekle',
blockchainConnected: 'Pezkuwichain Bağlı',
connectingBlockchain: "Blockchain'e Bağlanıyor...",
connectingRpc: 'RPC sunucusuna bağlanıyor...',
tokenNotFound: 'Token bulunamadı',
total: 'Toplam',
loadingBalance: 'Yükleniyor...',
},
// Errors
errors: {
networkError: 'İnternet bağlantısı yok. Lütfen bağlantınızı kontrol edin.',
timeout: 'İşlem çok uzun sürdü. Lütfen tekrar deneyin.',
walletNotFound: 'Cüzdan bulunamadı. Lütfen cüzdan oluşturun veya geri yükleyin.',
wrongPassword: 'Yanlış şifre. Lütfen tekrar deneyin.',
default: 'Bir şeyler yanlış gitti. Lütfen tekrar deneyin.',
},
// Validation
validation: {
minLength: 'Şifre en az 12 karakter olmalıdır',
needLowercase: 'Şifre en az 1 küçük harf içermelidir (a-z)',
needUppercase: 'Şifre en az 1 büyük harf içermelidir (A-Z)',
needNumber: 'Şifre en az 1 rakam içermelidir (0-9)',
needSpecialChar: 'Şifre en az 1 özel karakter içermelidir (!@#$%...)',
weakPassword:
'Şifre yeterince güçlü değil. Daha uzun ve çeşitli karakterler içeren bir şifre deneyin.',
},
// Time
time: {
now: 'Şimdi',
minutesAgo: '{count} dk önce',
hoursAgo: '{count} saat önce',
daysAgo: '{count} gün önce',
},
// Context
context: {
walletInitFailed: 'Cüzdan başlatılamadı',
rpcDisconnected: 'RPC bağlantısı kesildi. Yeniden bağlanılıyor...',
pleaseLoginFirst: 'Lütfen önce giriş yapın',
invalidSeedPhrase: 'Geçersiz seed phrase',
connectionFailed: 'Bağlantı başarısız',
referralStatsError: 'Referral istatistikleri yüklenemedi',
referralApproved: 'Referral onaylandı! Sayınız: {count}',
wrongPasswordError: 'Yanlış şifre',
walletSyncFailed: 'Cüzdan adresi DB ile senkronize edilemedi',
},
};
export default tr;
+275
View File
@@ -305,6 +305,281 @@ export interface Translations {
loadingScreen: {
loading: string;
};
// Dashboard (WalletDashboard main view)
dashboard: {
connected: string;
send: string;
receive: string;
presaleMessage: string;
depositUsdt: string;
depositUsdtDesc: string;
recentActivity: string;
history: string;
refreshTx: string;
loadingTx: string;
noRecentTx: string;
historyAppears: string;
sent: string;
received: string;
selectStaking: string;
validatorNominate: string;
trustScorePlus: string;
lpStakeDesc: string;
pezRewardPlus: string;
goBack: string;
};
// Send Tab
send: {
back: string;
selectToken: string;
whichToken: string;
someChainNotConnected: string;
transferSuccess: string;
wasSent: string;
done: string;
sendToken: string;
changeToken: string;
recipientAddress: string;
scanQr: string;
scanQrText: string;
qrNotAvailable: string;
invalidAddress: string;
amount: string;
balanceLabel: string;
sending: string;
sendButton: string;
walletNotReady: string;
selectTokenFirst: string;
fillAddressAndAmount: string;
insufficientBalance: string;
mainnetApiNotReady: string;
assetHubApiNotReady: string;
transferFailed: string;
};
// Receive Tab
receive: {
title: string;
back: string;
qrFailed: string;
shareAddress: string;
addressCopied: string;
copyAddress: string;
};
// History Tab
history: {
title: string;
back: string;
loadingTx: string;
noTransactions: string;
historyAppears: string;
sent: string;
received: string;
to: string;
from: string;
};
// Swap Modal
swap: {
title: string;
fromLabel: string;
toLabel: string;
exchangeRate: string;
noPool: string;
swapping: string;
noPoolButton: string;
swapButton: string;
swapFailed: string;
swapSuccess: string;
balanceLabel: string;
insufficientBalance: string;
};
// Pools Modal
pools: {
title: string;
connectionError: string;
loadingPools: string;
noPools: string;
back: string;
addLiquidity: string;
removeLiquidity: string;
yourPosition: string;
addButton: string;
removeButton: string;
reserve: string;
lpBalance: string;
lpTokenAmount: string;
amountAuto: string;
adding: string;
removing: string;
addFailed: string;
removeFailed: string;
invalidLpAmount: string;
estimatedReturn: string;
success: string;
addedLiquidity: string;
removedLiquidity: string;
};
// HEZ Staking Modal
staking: {
palletNotFound: string;
fetchError: string;
statusTab: string;
bondTab: string;
nominateTab: string;
unbondTab: string;
activeStake: string;
totalBonded: string;
nominations: string;
rewardDestination: string;
unbondingChunks: string;
nominatedValidators: string;
stakingTip: string;
notStakedYet: string;
stakeForTrustScore: string;
startStaking: string;
yourBalance: string;
currentlyStaked: string;
amountHez: string;
bondWarning: string;
bonding: string;
bondExtra: string;
bondButton: string;
bondFirst: string;
selectValidators: string;
commission: string;
nominating: string;
nominateButton: string;
notStakedUnbond: string;
unbondWarning: string;
unbondProcessing: string;
unbondButton: string;
bondSuccess: string;
nominateSuccess: string;
unbondSuccess: string;
bondFailed: string;
nominateFailed: string;
unbondFailed: string;
};
// LP Staking Modal
lpStaking: {
palletNotReady: string;
poolsNotLoaded: string;
noPoolsYet: string;
selectPool: string;
totalStaked: string;
youStaked: string;
lpBalance: string;
reward: string;
stakeTab: string;
unstakeTab: string;
rewardTab: string;
amount: string;
balanceLabel: string;
stakedLabel: string;
staking: string;
stakeButton: string;
unstaking: string;
unstakeButton: string;
pendingRewards: string;
claiming: string;
claimButton: string;
stakeFailed: string;
stakeSuccess: string;
unstakeFailed: string;
unstakeSuccess: string;
claimFailed: string;
claimSuccess: string;
};
// Fund Fees Modal
fees: {
title: string;
subtitle: string;
success: string;
sentTo: string;
failed: string;
tryAgain: string;
targetChain: string;
forTransfers: string;
forIdentity: string;
minRecommended: string;
amountHez: string;
signing: string;
xcmTeleportPending: string;
signingButton: string;
processing: string;
sendTo: string;
walletNotConnected: string;
apiNotConnected: string;
enterValidAmount: string;
chainNotConnected: string;
insufficientBalance: string;
xcmPalletNotFound: string;
teleportFailed: string;
errorOccurred: string;
};
// Tokens Card
tokens: {
searchPlaceholder: string;
addToken: string;
assetIdPlaceholder: string;
cancel: string;
add: string;
blockchainConnected: string;
connectingBlockchain: string;
connectingRpc: string;
tokenNotFound: string;
total: string;
loadingBalance: string;
};
// Error messages (error-tracking.ts)
errors: {
networkError: string;
timeout: string;
walletNotFound: string;
wrongPassword: string;
default: string;
};
// Password validation (crypto.ts)
validation: {
minLength: string;
needLowercase: string;
needUppercase: string;
needNumber: string;
needSpecialChar: string;
weakPassword: string;
};
// Time formatting (utils.ts)
time: {
now: string;
minutesAgo: string;
hoursAgo: string;
daysAgo: string;
};
// Context messages (WalletContext, ReferralContext, wallet-storage)
context: {
walletInitFailed: string;
rpcDisconnected: string;
pleaseLoginFirst: string;
invalidSeedPhrase: string;
connectionFailed: string;
referralStatsError: string;
referralApproved: string;
wrongPasswordError: string;
walletSyncFailed: string;
};
}
export type LanguageCode = 'krd' | 'en' | 'tr' | 'ckb' | 'fa' | 'ar';
+8 -7
View File
@@ -10,6 +10,8 @@
* - Version header for future algorithm updates
*/
import { translate } from '@/i18n';
const SALT_LENGTH = 16;
const IV_LENGTH = 12;
const VERSION_LENGTH = 1;
@@ -162,12 +164,12 @@ export function validatePassword(password: string): {
const strength = getPasswordStrength(password);
if (password.length < 12) {
return { valid: false, message: 'Şîfre (password) herî kêm 12 tîp be', entropy, strength };
return { valid: false, message: translate('validation.minLength'), entropy, strength };
}
if (!/[a-z]/.test(password)) {
return {
valid: false,
message: 'Şîfre (password) herî kêm 1 tîpa biçûk hebe (a-z)',
message: translate('validation.needLowercase'),
entropy,
strength,
};
@@ -175,7 +177,7 @@ export function validatePassword(password: string): {
if (!/[A-Z]/.test(password)) {
return {
valid: false,
message: 'Şîfre (password) herî kêm 1 tîpa mezin hebe (A-Z)',
message: translate('validation.needUppercase'),
entropy,
strength,
};
@@ -183,7 +185,7 @@ export function validatePassword(password: string): {
if (!/[0-9]/.test(password)) {
return {
valid: false,
message: 'Şîfre (password) herî kêm 1 hejmar hebe (0-9)',
message: translate('validation.needNumber'),
entropy,
strength,
};
@@ -191,7 +193,7 @@ export function validatePassword(password: string): {
if (!/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
return {
valid: false,
message: 'Şîfre (password) herî kêm 1 nîşana taybet hebe (!@#$%...)',
message: translate('validation.needSpecialChar'),
entropy,
strength,
};
@@ -199,8 +201,7 @@ export function validatePassword(password: string): {
if (entropy < 60) {
return {
valid: false,
message:
'Şîfre (password) ne têra qewî ye. Şîfreyek (password) dirêjtir bi tîpên cûrbecûr biceribîne.',
message: translate('validation.weakPassword'),
entropy,
strength,
};
+11 -9
View File
@@ -8,6 +8,8 @@
* - Custom analytics endpoint
*/
import { translate } from '@/i18n';
export interface ErrorContext {
component?: string;
action?: string;
@@ -118,20 +120,20 @@ export function extractError(caught: unknown): Error {
* Format error for user display
*/
export function formatUserError(error: Error): string {
// Map technical errors to user-friendly messages
// Map technical errors to translation keys
const errorMap: Record<string, string> = {
'Network Error': 'Têkiliya înternetê tune ye. Ji kerema xwe têkiliya xwe kontrol bike.',
'Failed to fetch': 'Têkiliya înternetê tune ye. Ji kerema xwe têkiliya xwe kontrol bike.',
TIMEOUT: 'Operasyon zêde dirêj kişand. Ji kerema xwe dîsa biceribîne.',
'Wallet not found': 'Wallet nehate dîtin. Ji kerema xwe wallet çêke an jî restore bike.',
'Şîfre (password) çewt e': 'Şîfre (password) çewt e. Ji kerema xwe dîsa biceribîne.',
'Network Error': 'errors.networkError',
'Failed to fetch': 'errors.networkError',
TIMEOUT: 'errors.timeout',
'Wallet not found': 'errors.walletNotFound',
'Şîfre (password) çewt e': 'errors.wrongPassword',
};
for (const [key, message] of Object.entries(errorMap)) {
for (const [key, translationKey] of Object.entries(errorMap)) {
if (error.message.includes(key)) {
return message;
return translate(translationKey);
}
}
return 'Tiştek çewt çêbû. Ji kerema xwe dîsa biceribîne.';
return translate('errors.default');
}
+15 -5
View File
@@ -1,5 +1,6 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { translate, getCurrentLanguage } from '@/i18n';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -25,12 +26,21 @@ export function formatDate(date: Date | string): string {
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 1) return 'Niha';
if (minutes < 60) return `${minutes} deq berê`;
if (hours < 24) return `${hours} saet berê`;
if (days < 7) return `${days} roj berê`;
if (minutes < 1) return translate('time.now');
if (minutes < 60) return translate('time.minutesAgo', { count: minutes });
if (hours < 24) return translate('time.hoursAgo', { count: hours });
if (days < 7) return translate('time.daysAgo', { count: days });
return d.toLocaleDateString('ku', { day: 'numeric', month: 'short' });
const langMap: Record<string, string> = {
krd: 'ku',
en: 'en',
tr: 'tr',
ckb: 'ckb',
fa: 'fa',
ar: 'ar',
};
const locale = langMap[getCurrentLanguage()] || 'ku';
return d.toLocaleDateString(locale, { day: 'numeric', month: 'short' });
}
export function generateId(): string {
+3 -2
View File
@@ -4,6 +4,7 @@
*/
import { encrypt, decrypt } from './crypto';
import { translate } from '@/i18n';
const STORAGE_KEY = 'pezkuwi_wallet';
@@ -72,7 +73,7 @@ export async function unlockWallet(password: string): Promise<string> {
const mnemonic = await decrypt(wallet.encryptedMnemonic, password);
return mnemonic;
} catch {
throw new Error('Şîfre (password) çewt e');
throw new Error(translate('context.wrongPasswordError'));
}
}
@@ -116,6 +117,6 @@ export async function syncWalletToSupabase(
if (error) {
console.error('Wallet sync error:', error);
throw new Error('Wallet adresa DB-ê re senkronîze nebû');
throw new Error(translate('context.walletSyncFailed'));
}
}
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "1.0.189",
"buildTime": "2026-02-14T08:53:06.213Z",
"buildNumber": 1771059186214
"version": "1.0.190",
"buildTime": "2026-02-14T15:16:08.944Z",
"buildNumber": 1771082168945
}