feat: add i18n support with 6 languages (en, tr, krd, ar, fa, ckb)

- Add translation system with useTranslation hook and LanguageProvider
- Auto-detect language from Telegram user settings
- Update all components and sections to use translation keys
- Support English, Turkish, Kurdish, Arabic, Persian, Sorani
This commit is contained in:
2026-02-14 11:06:14 +03:00
parent e5dd2b4b5b
commit 9da348bdf3
26 changed files with 2682 additions and 424 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pezkuwi-telegram-miniapp",
"version": "1.0.185",
"version": "1.0.186",
"type": "module",
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
"author": "Pezkuwichain Team",
+9 -7
View File
@@ -5,6 +5,7 @@ import { UpdateNotification } from '@/components/UpdateNotification';
import { P2PModal } from '@/components/P2PModal';
import { useAuth } from '@/contexts/AuthContext';
import { useWallet } from '@/contexts/WalletContext';
import { useTranslation } from '@/i18n';
// Lazy load sections for code splitting
const AnnouncementsSection = lazy(() =>
@@ -35,16 +36,16 @@ type NavId = Section | 'p2p';
interface NavItem {
id: NavId;
icon: typeof Megaphone;
label: string;
labelKey: string;
isExternal?: boolean;
}
const NAV_ITEMS: NavItem[] = [
{ id: 'announcements', icon: Megaphone, label: 'Ragihandin' },
{ id: 'forum', icon: MessageCircle, label: 'Forum' },
{ id: 'rewards', icon: Gift, label: 'Xelat' },
{ id: 'p2p', icon: ArrowLeftRight, label: 'P2P', isExternal: true },
{ id: 'wallet', icon: Wallet, label: 'Berîk' },
{ id: 'announcements', icon: Megaphone, labelKey: 'nav.announcements' },
{ id: 'forum', icon: MessageCircle, labelKey: 'nav.forum' },
{ id: 'rewards', icon: Gift, labelKey: 'nav.rewards' },
{ id: 'p2p', icon: ArrowLeftRight, labelKey: 'nav.p2p', isExternal: true },
{ id: 'wallet', icon: Wallet, labelKey: 'nav.wallet' },
];
// P2P Web App URL - Mobile-optimized P2P
@@ -55,6 +56,7 @@ export default function App() {
const [showP2PModal, setShowP2PModal] = useState(false);
const { sessionToken } = useAuth();
const { address } = useWallet();
const { t } = useTranslation();
// Open P2P in popup with auth params
const openP2P = useCallback(() => {
@@ -127,7 +129,7 @@ export default function App() {
)}
>
<Icon className={cn('w-5 h-5', isActive && 'scale-110')} />
<span className="text-[10px] font-medium">{item.label}</span>
<span className="text-[10px] font-medium">{t(item.labelKey)}</span>
</button>
);
})}
+20 -3
View File
@@ -1,6 +1,21 @@
import { Component, ErrorInfo, ReactNode } from 'react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
import { trackError } from '@/lib/error-tracking';
import krd from '@/i18n/translations/krd';
import en from '@/i18n/translations/en';
import tr from '@/i18n/translations/tr';
import ckb from '@/i18n/translations/ckb';
import fa from '@/i18n/translations/fa';
import ar from '@/i18n/translations/ar';
import type { Translations, LanguageCode } from '@/i18n/types';
const translations: Record<LanguageCode, Translations> = { krd, en, tr, ckb, fa, ar };
function detectLang(): LanguageCode {
const seg = window.location.pathname.split('/').filter(Boolean)[0];
if (seg && seg in translations) return seg as LanguageCode;
return 'krd';
}
interface Props {
children: ReactNode;
@@ -52,16 +67,18 @@ export class ErrorBoundary extends Component<Props, State> {
<div className="w-16 h-16 rounded-full bg-red-500/20 flex items-center justify-center mb-4">
<AlertTriangle className="w-8 h-8 text-red-400" />
</div>
<h1 className="text-xl font-semibold mb-2">Tiştek çewt çê</h1>
<h1 className="text-xl font-semibold mb-2">
{translations[detectLang()].errorBoundary.title}
</h1>
<p className="text-sm text-muted-foreground text-center mb-6 max-w-xs">
Bibore, pirsgirêkek teknîkî derket. Ji kerema xwe dîsa biceribîne.
{translations[detectLang()].errorBoundary.description}
</p>
<button
onClick={this.handleRetry}
className="flex items-center gap-2 px-4 py-2 bg-primary rounded-lg text-primary-foreground font-medium"
>
<RefreshCw className="w-4 h-4" />
Dîsa biceribîne
{translations[detectLang()].errorBoundary.retry}
</button>
{import.meta.env.DEV && this.state.error && (
<div className="mt-6 w-full max-w-lg">
+5 -2
View File
@@ -1,10 +1,13 @@
import { Loader2 } from 'lucide-react';
import { useTranslation } from '@/i18n';
interface LoadingScreenProps {
message?: string;
}
export function LoadingScreen({ message = 'Tê barkirin...' }: LoadingScreenProps) {
export function LoadingScreen({ message }: LoadingScreenProps) {
const { t } = useTranslation();
const displayMessage = message ?? t('loadingScreen.loading');
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-background">
<div className="relative">
@@ -12,7 +15,7 @@ export function LoadingScreen({ message = 'Tê barkirin...' }: LoadingScreenProp
<Loader2 className="w-8 h-8 text-primary animate-spin" />
</div>
</div>
<p className="mt-4 text-sm text-muted-foreground">{message}</p>
<p className="mt-4 text-sm text-muted-foreground">{displayMessage}</p>
</div>
);
}
+16 -102
View File
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { X, ExternalLink, Info } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useTranslation } from '@/i18n';
interface P2PModalProps {
isOpen: boolean;
@@ -8,84 +8,8 @@ interface P2PModalProps {
onOpenP2P: () => void;
}
type Language = 'en' | 'ckb' | 'ku' | 'tr';
const LANGUAGES: { code: Language; label: string }[] = [
{ code: 'en', label: 'EN' },
{ code: 'ckb', label: 'سۆرانی' },
{ code: 'ku', label: 'Kurmancî' },
{ code: 'tr', label: 'TR' },
];
const CONTENT: Record<
Language,
{
title: string;
subtitle: string;
firstTime: string;
steps: string[];
note: string;
button: string;
}
> = {
en: {
title: 'P2P Exchange',
subtitle: 'Trade crypto peer-to-peer',
firstTime: 'First time using P2P?',
steps: [
'Click the button below to open the web app',
'Create an account or log in',
'Complete the P2P setup process',
'After setup, you can access P2P directly',
],
note: 'The web app will open in a new window. Complete the registration process there.',
button: 'Open P2P Platform',
},
ckb: {
title: 'P2P ئاڵۆگۆڕ',
subtitle: 'ئاڵۆگۆڕی کریپتۆ لە نێوان کەسەکاندا',
firstTime: 'یەکەم جار P2P بەکاردەهێنیت؟',
steps: [
'کلیک لە دوگمەی خوارەوە بکە بۆ کردنەوەی ماڵپەڕ',
'هەژمارێک دروست بکە یان بچۆ ژوورەوە',
'پرۆسەی دامەزراندنی P2P تەواو بکە',
'دوای دامەزراندن، دەتوانیت ڕاستەوخۆ بچیتە P2P',
],
note: 'ماڵپەڕ لە پەنجەرەیەکی نوێ دەکرێتەوە. پرۆسەی تۆمارکردن لەوێ تەواو بکە.',
button: 'کردنەوەی P2P',
},
ku: {
title: 'P2P Danûstandin',
subtitle: 'Danûstandina krîpto di navbera kesan de',
firstTime: 'Cara yekem P2P bikar tînin?',
steps: [
'Li bişkoja jêrîn bikirtînin da ku malpera webê vebike',
'Hesabek çêbikin an têkevin',
'Pêvajoya sazkirina P2P temam bikin',
'Piştî sazkirinê, hûn dikarin rasterast bigihîjin P2P',
],
note: 'Malpera webê di pencereyek nû de vedibe. Pêvajoya qeydkirinê li wir temam bikin.',
button: 'P2P Veke',
},
tr: {
title: 'P2P Borsa',
subtitle: 'Kullanıcılar arası kripto alım satım',
firstTime: "P2P'yi ilk kez mi kullanıyorsunuz?",
steps: [
'Web uygulamasını açmak için aşağıdaki butona tıklayın',
'Hesap oluşturun veya giriş yapın',
'P2P kurulum sürecini tamamlayın',
"Kurulumdan sonra P2P'ye doğrudan erişebilirsiniz",
],
note: 'Web uygulaması yeni bir pencerede açılacak. Kayıt işlemini orada tamamlayın.',
button: 'P2P Platformunu Aç',
},
};
export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
const [lang, setLang] = useState<Language>('en');
const content = CONTENT[lang];
const isRTL = lang === 'ckb';
const { t, isRTL } = useTranslation();
if (!isOpen) return null;
@@ -94,6 +18,14 @@ export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
onClose();
};
// Access steps array directly - t() only works for string values
// We need to get the steps from the translation as individual indexed items
const steps: string[] = [];
for (let i = 0; i < 4; i++) {
const step = t(`p2p.steps.${i}`);
if (step !== `p2p.steps.${i}`) steps.push(step);
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm">
<div
@@ -105,42 +37,24 @@ export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-border">
<h2 className="text-lg font-semibold text-foreground">{content.title}</h2>
<h2 className="text-lg font-semibold text-foreground">{t('p2p.title')}</h2>
<button onClick={onClose} className="p-2 rounded-full hover:bg-muted transition-colors">
<X className="w-5 h-5 text-muted-foreground" />
</button>
</div>
{/* Language Selector */}
<div className="flex gap-2 p-4 pb-0">
{LANGUAGES.map((l) => (
<button
key={l.code}
onClick={() => setLang(l.code)}
className={cn(
'px-3 py-1.5 rounded-full text-xs font-medium transition-colors',
lang === l.code
? 'bg-primary text-primary-foreground'
: 'bg-muted text-muted-foreground hover:bg-muted/80'
)}
>
{l.label}
</button>
))}
</div>
{/* Content */}
<div className="p-4 space-y-4">
<p className="text-sm text-muted-foreground">{content.subtitle}</p>
<p className="text-sm text-muted-foreground">{t('p2p.subtitle')}</p>
{/* First Time Info */}
<div className="bg-amber-500/10 border border-amber-500/30 rounded-xl p-4">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-amber-400 flex-shrink-0 mt-0.5" />
<div className="space-y-2">
<p className="text-sm font-medium text-amber-400">{content.firstTime}</p>
<p className="text-sm font-medium text-amber-400">{t('p2p.firstTime')}</p>
<ol className="space-y-1.5">
{content.steps.map((step, i) => (
{steps.map((step, i) => (
<li key={i} className="text-xs text-amber-200/80 flex gap-2">
<span className="font-semibold text-amber-400">{i + 1}.</span>
<span>{step}</span>
@@ -152,7 +66,7 @@ export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
</div>
{/* Note */}
<p className="text-xs text-muted-foreground">{content.note}</p>
<p className="text-xs text-muted-foreground">{t('p2p.note')}</p>
</div>
{/* Action Button */}
@@ -162,7 +76,7 @@ export function P2PModal({ isOpen, onClose, onOpenP2P }: P2PModalProps) {
className="w-full flex items-center justify-center gap-2 py-3 px-4 bg-cyan-500 hover:bg-cyan-600 text-white font-medium rounded-xl transition-colors"
>
<ExternalLink className="w-5 h-5" />
{content.button}
{t('p2p.button')}
</button>
</div>
</div>
+14 -14
View File
@@ -1,12 +1,13 @@
import { ExternalLink } from 'lucide-react';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
interface SocialLink {
name: string;
url: string;
icon: string;
color: string;
description: string;
descriptionKey: string;
}
const SOCIAL_LINKS: SocialLink[] = [
@@ -15,61 +16,62 @@ const SOCIAL_LINKS: SocialLink[] = [
url: 'https://www.instagram.com/pezkuwichain',
icon: '📸',
color: 'from-pink-500 to-purple-600',
description: 'Wêne û Story',
descriptionKey: 'social.instagram',
},
{
name: 'TikTok',
url: 'https://www.tiktok.com/@pezkuwi.chain',
icon: '🎵',
color: 'from-gray-800 to-gray-900',
description: 'Vîdyoyên kurt',
descriptionKey: 'social.tiktok',
},
{
name: 'Snapchat',
url: 'https://www.snapchat.com/add/pezkuwichain',
icon: '👻',
color: 'from-yellow-400 to-yellow-500',
description: 'Snap bike!',
descriptionKey: 'social.snapchat',
},
{
name: 'Telegram',
url: 'https://t.me/pezkuwichain',
icon: '📢',
color: 'from-blue-400 to-blue-600',
description: 'Kanala fermî',
descriptionKey: 'social.telegram',
},
{
name: 'X (Twitter)',
url: 'https://x.com/pezkuwichain',
icon: '𝕏',
color: 'from-gray-700 to-gray-900',
description: 'Nûçeyên rojane',
descriptionKey: 'social.twitter',
},
{
name: 'YouTube',
url: 'https://www.youtube.com/@SatoshiQazi',
icon: '▶️',
color: 'from-red-500 to-red-700',
description: 'Vîdyoyên me',
descriptionKey: 'social.youtube',
},
{
name: 'Facebook',
url: 'https://www.facebook.com/people/Pezkuwi-Chain/61587122224932/',
icon: '📘',
color: 'from-blue-600 to-blue-800',
description: 'Rûpela fermî',
descriptionKey: 'social.facebook',
},
{
name: 'Discord',
url: 'https://discord.gg/Y3VyEC6h8W',
icon: '💬',
color: 'from-indigo-500 to-purple-600',
description: 'Civaka me',
descriptionKey: 'social.discord',
},
];
export function SocialLinks() {
const { openLink, hapticImpact } = useTelegram();
const { t } = useTranslation();
const handleClick = (url: string) => {
hapticImpact('light');
@@ -80,11 +82,9 @@ export function SocialLinks() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<h3 className="font-medium text-foreground mb-3 flex items-center gap-2">
<ExternalLink className="w-4 h-4 text-primary" />
Me bişopîne
{t('social.followUs')}
</h3>
<p className="text-sm text-muted-foreground mb-4">
Bi me re têkiliyê ragire û nûçeyên herî dawî bistîne!
</p>
<p className="text-sm text-muted-foreground mb-4">{t('social.stayConnected')}</p>
<div className="grid grid-cols-2 gap-2">
{SOCIAL_LINKS.map((link) => (
<button
@@ -100,7 +100,7 @@ export function SocialLinks() {
<span className="text-2xl">{link.icon}</span>
<div className="text-left">
<p className="text-sm font-medium text-white">{link.name}</p>
<p className="text-xs text-white/70">{link.description}</p>
<p className="text-xs text-white/70">{t(link.descriptionKey)}</p>
</div>
</button>
))}
+6 -6
View File
@@ -6,10 +6,12 @@
import { RefreshCw, X } from 'lucide-react';
import { useVersion } from '@/hooks/useVersion';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
export function UpdateNotification() {
const { hasUpdate, forceUpdate, dismissUpdate, currentVersion } = useVersion();
const { hapticImpact } = useTelegram();
const { t } = useTranslation();
if (!hasUpdate) return null;
@@ -31,10 +33,8 @@ export function UpdateNotification() {
<RefreshCw className="w-5 h-5" />
</div>
<div className="flex-1 min-w-0">
<h4 className="font-semibold text-sm">Guhertoya heye!</h4>
<p className="text-xs opacity-90 mt-0.5">
Ji bo taybetmendiyên û rastkirinên ewlehiyê nûve bike.
</p>
<h4 className="font-semibold text-sm">{t('update.newVersion')}</h4>
<p className="text-xs opacity-90 mt-0.5">{t('update.description')}</p>
<p className="text-[10px] opacity-70 mt-1">v{currentVersion}</p>
</div>
<button
@@ -50,14 +50,14 @@ export function UpdateNotification() {
onClick={handleDismiss}
className="flex-1 py-2 px-3 rounded-lg bg-white/10 hover:bg-white/20 text-sm font-medium transition-colors"
>
Paşê
{t('update.later')}
</button>
<button
onClick={handleUpdate}
className="flex-1 py-2 px-3 rounded-lg bg-white text-primary text-sm font-medium hover:bg-white/90 transition-colors flex items-center justify-center gap-2"
>
<RefreshCw className="w-4 h-4" />
Nûve bike
{t('update.updateNow')}
</button>
</div>
</div>
+50 -50
View File
@@ -18,6 +18,7 @@ import {
import { useTelegram } from '@/hooks/useTelegram';
import { useWallet } from '@/contexts/WalletContext';
import { supabase } from '@/lib/supabase';
import { useTranslation } from '@/i18n';
type Network = 'ton' | 'polkadot' | 'trc20';
@@ -28,7 +29,7 @@ interface NetworkInfo {
icon: string;
recommended: boolean;
fee: number;
feeWarning?: string;
feeWarning?: boolean;
explorer: string;
minDeposit: number;
}
@@ -61,7 +62,7 @@ const NETWORKS: NetworkInfo[] = [
icon: '🔴',
recommended: false,
fee: 3,
feeWarning: 'Mesrefa tora TRC20 bi qasî $3 ye. Em tora TON an Polkadot pêşniyar dikin.',
feeWarning: true,
explorer: 'https://tronscan.org/#/transaction/',
minDeposit: 10,
},
@@ -84,6 +85,7 @@ interface Props {
export function DepositUSDTModal({ isOpen, onClose }: Props) {
const { hapticImpact, showAlert } = useTelegram();
const { address: localWalletAddress } = useWallet();
const { t } = useTranslation();
const [selectedNetwork, setSelectedNetwork] = useState<Network>('ton');
const [depositCode, setDepositCode] = useState<string>('');
@@ -184,7 +186,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
hapticImpact('light');
setTimeout(() => setCopied(null), 2000);
} catch {
showAlert('Kopî nekir');
showAlert(t('deposit.copyFailed'));
}
};
@@ -204,15 +206,15 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
const getStatusText = (status: string) => {
switch (status) {
case 'pending':
return 'Li benda';
return t('deposit.statusPending');
case 'confirming':
return 'Tê pejirandin';
return t('deposit.statusConfirming');
case 'completed':
return 'Qediya';
return t('deposit.statusCompleted');
case 'failed':
return 'Neserketî';
return t('deposit.statusFailed');
case 'expired':
return 'Dema wê derbas bû';
return t('deposit.statusExpired');
default:
return status;
}
@@ -233,8 +235,8 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<Plus className="w-5 h-5 text-green-400" />
</div>
<div>
<h2 className="text-lg font-semibold">USDT Depo Bike</h2>
<p className="text-xs text-muted-foreground">Bo wUSDT li Asset Hub</p>
<h2 className="text-lg font-semibold">{t('deposit.title')}</h2>
<p className="text-xs text-muted-foreground">{t('deposit.subtitle')}</p>
</div>
</div>
<div className="flex gap-2">
@@ -256,9 +258,11 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
{showHistory ? (
/* Deposit History */
<div className="space-y-3">
<h3 className="text-sm font-medium text-muted-foreground">Dîroka Depoyan</h3>
<h3 className="text-sm font-medium text-muted-foreground">
{t('deposit.depositHistory')}
</h3>
{deposits.length === 0 ? (
<p className="text-center text-muted-foreground py-8">Hîn depo tune</p>
<p className="text-center text-muted-foreground py-8">{t('deposit.noDeposits')}</p>
) : (
deposits.map((deposit) => (
<div key={deposit.id} className="bg-muted/50 rounded-xl p-3">
@@ -290,7 +294,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
className="text-xs text-blue-400 flex items-center gap-1 mt-2"
>
<ExternalLink className="w-3 h-3" />
TX bibîne
{t('deposit.viewTx')}
</a>
)}
</div>
@@ -300,14 +304,16 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
onClick={() => setShowHistory(false)}
className="w-full py-3 bg-muted rounded-xl text-center"
>
Vegere
{t('deposit.goBack')}
</button>
</div>
) : (
<div className="space-y-4">
{/* Network Selection */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Torê Hilbijêre</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('deposit.selectNetwork')}
</label>
<div className="grid grid-cols-3 gap-2">
{NETWORKS.map((net) => (
<button
@@ -327,7 +333,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
>
{net.recommended && (
<span className="absolute -top-2 left-1/2 -translate-x-1/2 text-[10px] bg-green-500 text-white px-1.5 py-0.5 rounded">
Pêşniyar
{t('deposit.recommended')}
</span>
)}
<div className="text-xl mb-1">{net.icon}</div>
@@ -349,11 +355,13 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<div className="flex gap-3">
<AlertTriangle className="w-6 h-6 text-yellow-400 flex-shrink-0" />
<div>
<p className="text-sm text-yellow-400 font-medium mb-2">Dikkkat!</p>
<p className="text-xs text-yellow-400/80 mb-3">{network.feeWarning}</p>
<p className="text-xs text-yellow-400/80 mb-3">
Mînak: 10 USDT bişîne 7 wUSDT werbigire ($3 masraf)
<p className="text-sm text-yellow-400 font-medium mb-2">
{t('deposit.warning')}
</p>
<p className="text-xs text-yellow-400/80 mb-3">
{t('deposit.trc20FeeWarning')}
</p>
<p className="text-xs text-yellow-400/80 mb-3">{t('deposit.example')}</p>
<button
onClick={() => {
setTrc20Accepted(true);
@@ -361,7 +369,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
}}
className="w-full py-2 bg-yellow-500/20 border border-yellow-500/50 rounded-lg text-yellow-400 text-sm font-medium"
>
Qebûl dikim, bi TRC20 bişîne
{t('deposit.acceptTrc20')}
</button>
</div>
</div>
@@ -376,19 +384,13 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<div className="flex gap-2">
<AlertCircle className="w-5 h-5 text-blue-400 flex-shrink-0" />
<div className="text-sm text-blue-400">
<p className="font-medium">Girîng!</p>
<p className="font-medium">{t('deposit.important')}</p>
<ul className="list-disc list-inside text-xs mt-1 space-y-1">
<li>
Kêmtirîn: <strong>{network.minDeposit} USDT</strong>
</li>
{selectedNetwork !== 'trc20' && (
<li>Memo/Comment qada de koda xwe binivîse</li>
)}
<li>Tenê USDT bişîne, tokenên din winda dibin</li>
<li>{t('deposit.minimum', { amount: network.minDeposit })}</li>
{selectedNetwork !== 'trc20' && <li>{t('deposit.memoRequired')}</li>}
<li>{t('deposit.onlyUsdt')}</li>
{selectedNetwork === 'trc20' && (
<li className="text-yellow-400">
$3 masraf ji mîqdara we kêmkirin
</li>
<li className="text-yellow-400">{t('deposit.trc20Fee')}</li>
)}
</ul>
</div>
@@ -399,7 +401,9 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<div className="bg-muted/50 rounded-xl p-4 space-y-4">
{/* Address */}
<div>
<label className="text-xs text-muted-foreground">Navnîşana Depoyê</label>
<label className="text-xs text-muted-foreground">
{t('deposit.depositAddress')}
</label>
<div className="flex items-center gap-2 mt-1">
{isLoading && selectedNetwork === 'trc20' ? (
<div className="flex-1 p-2 bg-background rounded-lg flex justify-center">
@@ -407,7 +411,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
</div>
) : (
<code className="flex-1 text-xs font-mono bg-background p-2 rounded-lg break-all">
{currentAddress || 'Amade nîne'}
{currentAddress || t('deposit.notAvailable')}
</code>
)}
<button
@@ -428,7 +432,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
{selectedNetwork !== 'trc20' && (
<div>
<label className="text-xs text-muted-foreground">
Memo / Comment (PÊWÎST)
{t('deposit.memoLabel')}
</label>
<div className="flex items-center gap-2 mt-1">
{isLoading ? (
@@ -452,31 +456,27 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
)}
</button>
</div>
<p className="text-xs text-red-400 mt-1">
kodê di memo de binivîse, wekî din depoya te nayê nas kirin!
</p>
<p className="text-xs text-red-400 mt-1">{t('deposit.memoWarning')}</p>
</div>
)}
{selectedNetwork === 'trc20' && (
<p className="text-xs text-green-400">
Ev navnîşan tenê ya te ye. Memo ne pêwîst e.
</p>
<p className="text-xs text-green-400">{t('deposit.uniqueAddress')}</p>
)}
</div>
{/* Steps */}
<div className="bg-muted/30 rounded-xl p-4">
<h4 className="text-sm font-medium mb-3">Çawa Depo Bikim?</h4>
<h4 className="text-sm font-medium mb-3">{t('deposit.howToDeposit')}</h4>
<ol className="text-xs text-muted-foreground space-y-2">
<li className="flex gap-2">
<span className="text-green-400 font-bold">1.</span>
<span>Navnîşan kopî bike</span>
<span>{t('deposit.stepCopyAddress')}</span>
</li>
{selectedNetwork !== 'trc20' && (
<li className="flex gap-2">
<span className="text-green-400 font-bold">2.</span>
<span>Memo kodê kopî bike</span>
<span>{t('deposit.stepCopyMemo')}</span>
</li>
)}
<li className="flex gap-2">
@@ -485,30 +485,30 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
</span>
<span>
{selectedNetwork === 'ton'
? 'Telegram Wallet an cîzdana xwe veke'
? t('deposit.stepOpenTon')
: selectedNetwork === 'polkadot'
? 'Polkadot cîzdana xwe veke'
: 'TronLink an cîzdana xwe veke'}
? t('deposit.stepOpenPolkadot')
: t('deposit.stepOpenTrc20')}
</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">
{selectedNetwork === 'trc20' ? '3' : '4'}.
</span>
<span>USDT bişîne navnîşana jorîn</span>
<span>{t('deposit.stepSendUsdt')}</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">
{selectedNetwork === 'trc20' ? '4' : '5'}.
</span>
<span>wUSDT di nav çend hûrdeman de li hesabê te be</span>
<span>{t('deposit.stepReceive')}</span>
</li>
</ol>
</div>
{/* Processing Time */}
<p className="text-center text-xs text-muted-foreground">
Dema pêvajoyê: ~1-5 hûrdem
{t('deposit.processingTime')}
</p>
</>
)}
+16 -15
View File
@@ -8,6 +8,7 @@ import { Eye, EyeOff, Wallet, Unlock, Trash2 } from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { formatAddress } from '@/lib/wallet-service';
import { useTranslation } from '@/i18n';
interface Props {
onConnected: () => void;
@@ -17,6 +18,7 @@ interface Props {
export function WalletConnect({ onConnected, onDelete }: Props) {
const { address, connect, error: walletError } = useWallet();
const { hapticImpact, hapticNotification } = useTelegram();
const { t } = useTranslation();
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
@@ -26,7 +28,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
const handleConnect = async () => {
if (!password) {
setError('Şîfre (password) binivîse');
setError(t('walletConnect.enterPassword'));
return;
}
@@ -39,7 +41,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
hapticNotification('success');
onConnected();
} catch (err) {
setError(err instanceof Error ? err.message : 'Şîfre (password) çewt e');
setError(err instanceof Error ? err.message : t('walletConnect.wrongPassword'));
hapticNotification('error');
} finally {
setIsLoading(false);
@@ -58,11 +60,8 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
<div className="w-16 h-16 mx-auto bg-red-500/20 rounded-full flex items-center justify-center mb-4">
<Trash2 className="w-8 h-8 text-red-500" />
</div>
<h2 className="text-xl font-semibold mb-2">Wallet Bibe?</h2>
<p className="text-muted-foreground text-sm">
Ev çalakî nayê paşvekişandin. Eger seed phrase&apos;ê te tune be, tu nikarî gihîştina
wallet&apos;ê xwe bistînî.
</p>
<h2 className="text-xl font-semibold mb-2">{t('walletConnect.deleteTitle')}</h2>
<p className="text-muted-foreground text-sm">{t('walletConnect.deleteDescription')}</p>
</div>
<div className="flex gap-3">
@@ -70,13 +69,13 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
onClick={() => setShowDeleteConfirm(false)}
className="flex-1 py-3 bg-muted rounded-xl font-semibold"
>
Betal
{t('common.cancel')}
</button>
<button
onClick={handleDelete}
className="flex-1 py-3 bg-red-500 text-white rounded-xl font-semibold"
>
Bibe
{t('walletConnect.deleteButton')}
</button>
</div>
</div>
@@ -89,7 +88,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
<div className="w-16 h-16 mx-auto bg-primary/20 rounded-full flex items-center justify-center mb-4">
<Wallet className="w-8 h-8 text-primary" />
</div>
<h2 className="text-xl font-semibold mb-2">Wallet Veke</h2>
<h2 className="text-xl font-semibold mb-2">{t('walletConnect.openWallet')}</h2>
{address && (
<p className="text-muted-foreground text-sm font-mono">{formatAddress(address)}</p>
)}
@@ -97,7 +96,9 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfre (Password)</label>
<label className="text-sm text-muted-foreground">
{t('walletConnect.passwordLabel')}
</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
@@ -105,7 +106,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
onChange={(e) => setPassword(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleConnect()}
className="w-full px-4 py-3 bg-muted rounded-xl pr-12"
placeholder="Şîfre (password) binivîse"
placeholder={t('walletConnect.passwordPlaceholder')}
autoFocus
/>
<button
@@ -130,11 +131,11 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{isLoading ? (
'Tê vekirin...'
t('walletConnect.connecting')
) : (
<>
<Unlock className="w-4 h-4" />
Connect
{t('walletConnect.connect')}
</>
)}
</button>
@@ -143,7 +144,7 @@ export function WalletConnect({ onConnected, onDelete }: Props) {
onClick={() => setShowDeleteConfirm(true)}
className="w-full py-3 text-red-400 text-sm"
>
Wallet bibe
{t('walletConnect.deleteWalletLink')}
</button>
</div>
</div>
+54 -58
View File
@@ -17,6 +17,7 @@ import {
} from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
type Step = 'password' | 'backup' | 'verify' | 'complete';
@@ -34,6 +35,7 @@ interface Props {
export function WalletCreate({ onComplete, onBack }: Props) {
const { generateNewWallet, confirmWallet, isInitialized } = useWallet();
const { hapticImpact, hapticNotification } = useTelegram();
const { t } = useTranslation();
const [step, setStep] = useState<Step>('password');
const [password, setPassword] = useState('');
@@ -84,12 +86,12 @@ export function WalletCreate({ onComplete, onBack }: Props) {
setError('');
if (!isInitialized) {
setError('Wallet service amade nîne. Ji kerema xwe bisekinin.');
setError(t('walletCreate.walletServiceNotReady'));
return;
}
if (!allPasswordRulesPass) {
setError('Ji kerema xwe hemû şertên şîfre (password) bicîh bînin');
setError(t('walletCreate.passwordRequirementsNotMet'));
hapticNotification('error');
return;
}
@@ -104,9 +106,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
setStep('backup');
} catch (err) {
console.error('Wallet generation error:', err);
setError(
err instanceof Error ? err.message : 'Wallet çênebû. Ji kerema xwe dîsa biceribînin'
);
setError(err instanceof Error ? err.message : t('walletCreate.walletCreationFailed'));
hapticNotification('error');
}
};
@@ -122,7 +122,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
// Step 2: Backup - Proceed to verify (only if all conditions checked)
const handleBackupContinue = () => {
if (!allConditionsChecked) {
setError('Ji kerema xwe hemû şertan bipejirînin');
setError(t('walletCreate.acceptAllConditions'));
return;
}
@@ -183,7 +183,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
originalWords.every((word, idx) => word === enteredWords[idx]);
if (!isCorrect) {
setError('Rêza peyvan ne rast e. Ji kerema xwe dîsa biceribînin');
setError(t('walletCreate.wrongOrder'));
hapticNotification('error');
return;
}
@@ -197,7 +197,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
hapticNotification('success');
setStep('complete');
} catch (err) {
setError(err instanceof Error ? err.message : 'Wallet çênebû');
setError(err instanceof Error ? err.message : t('walletCreate.walletCreationFailed'));
hapticNotification('error');
} finally {
setIsLoading(false);
@@ -213,26 +213,26 @@ export function WalletCreate({ onComplete, onBack }: Props) {
<div className="p-4 space-y-6">
<button onClick={onBack} className="flex items-center gap-2 text-muted-foreground">
<ArrowLeft className="w-4 h-4" />
<span>Paş</span>
<span>{t('common.back')}</span>
</button>
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">Şîfre (Password) Diyar Bike</h2>
<p className="text-muted-foreground text-sm">
Ev şîfre (password) ji bo vekirina wallet&apos;ê were bikaranîn
</p>
<h2 className="text-xl font-semibold mb-2">{t('walletCreate.setPassword')}</h2>
<p className="text-muted-foreground text-sm">{t('walletCreate.passwordDescription')}</p>
</div>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfre (Password)</label>
<label className="text-sm text-muted-foreground">
{t('walletCreate.passwordLabel')}
</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl pr-12"
placeholder="Herî kêm 12 tîp (min 12 characters)"
placeholder={t('walletCreate.passwordPlaceholder')}
/>
<button
type="button"
@@ -245,20 +245,24 @@ export function WalletCreate({ onComplete, onBack }: Props) {
</div>
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfre Dubare (Confirm Password)</label>
<label className="text-sm text-muted-foreground">
{t('walletCreate.confirmPasswordLabel')}
</label>
<input
type={showPassword ? 'text' : 'password'}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl"
placeholder="Şîfre dubare binivîse (confirm password)"
placeholder={t('walletCreate.confirmPasswordPlaceholder')}
/>
</div>
{/* Real-time password strength indicator */}
{password.length > 0 && (
<div className="p-3 bg-muted/50 rounded-xl space-y-2">
<p className="text-xs text-muted-foreground font-medium">Şertên Şîfre (Password):</p>
<p className="text-xs text-muted-foreground font-medium">
{t('walletCreate.passwordRequirements')}
</p>
<div className="grid grid-cols-1 gap-1.5 text-xs">
<div
className={`flex items-center gap-2 ${passwordRules.minLength ? 'text-green-400' : 'text-red-400'}`}
@@ -268,7 +272,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 12 tîp (min 12 characters)</span>
<span>{t('walletCreate.ruleMinLength')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasLowercase ? 'text-green-400' : 'text-red-400'}`}
@@ -278,7 +282,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 tîpa biçûk (a-z)</span>
<span>{t('walletCreate.ruleLowercase')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasUppercase ? 'text-green-400' : 'text-red-400'}`}
@@ -288,7 +292,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 tîpa mezin (A-Z)</span>
<span>{t('walletCreate.ruleUppercase')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasNumber ? 'text-green-400' : 'text-red-400'}`}
@@ -298,7 +302,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 hejmar (0-9)</span>
<span>{t('walletCreate.ruleNumber')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasSpecialChar ? 'text-green-400' : 'text-red-400'}`}
@@ -308,7 +312,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 sembola taybetî (!@#$%...)</span>
<span>{t('walletCreate.ruleSpecialChar')}</span>
</div>
{confirmPassword.length > 0 && (
<div
@@ -319,7 +323,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Şîfre (password) hev digirin</span>
<span>{t('walletCreate.rulePasswordsMatch')}</span>
</div>
)}
</div>
@@ -338,12 +342,12 @@ export function WalletCreate({ onComplete, onBack }: Props) {
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{!isInitialized
? 'Tê amadekirin...'
? t('walletCreate.preparing')
: isLoading
? 'Tê çêkirin...'
? t('walletCreate.creating')
: allPasswordRulesPass
? 'Berdewam'
: 'Şertên şîfre (password) bicîh bînin'}
? t('common.continue')
: t('walletCreate.meetPasswordRequirements')}
{!isLoading && allPasswordRulesPass && isInitialized && (
<ArrowRight className="w-4 h-4" />
)}
@@ -359,18 +363,13 @@ export function WalletCreate({ onComplete, onBack }: Props) {
return (
<div className="p-4 space-y-6">
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">Seed Phrase Paşguh Bike</h2>
<p className="text-muted-foreground text-sm">
Ev 12 peyv wallet&apos;ê te ne. Wan li cihekî ewle binivîse!
</p>
<h2 className="text-xl font-semibold mb-2">{t('walletCreate.backupTitle')}</h2>
<p className="text-muted-foreground text-sm">{t('walletCreate.backupDescription')}</p>
</div>
<div className="p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-xl flex items-start gap-3">
<AlertTriangle className="w-5 h-5 text-yellow-500 shrink-0 mt-0.5" />
<p className="text-sm text-yellow-200">
<strong>Girîng:</strong> Ev peyvan tenê yek car têne xuyang kirin. Eger te ev peyv winda
bikin, tu nikarî gihîştina wallet&apos;ê xwe bistînî.
</p>
<p className="text-sm text-yellow-200">{t('walletCreate.backupWarning')}</p>
</div>
<div className="grid grid-cols-3 gap-2">
@@ -389,12 +388,12 @@ export function WalletCreate({ onComplete, onBack }: Props) {
{copied ? (
<>
<Check className="w-4 h-4 text-green-400" />
<span className="text-green-400">Hat kopîkirin!</span>
<span className="text-green-400">{t('walletCreate.copiedMnemonic')}</span>
</>
) : (
<>
<Copy className="w-4 h-4" />
<span>Kopî Bike</span>
<span>{t('walletCreate.copyMnemonic')}</span>
</>
)}
</button>
@@ -410,7 +409,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
}
className="mt-1 w-5 h-5 accent-primary"
/>
<span className="text-sm">Min ev 12 peyv li cihekî ewle nivîsandine</span>
<span className="text-sm">{t('walletCreate.conditionWrittenDown')}</span>
</label>
<label className="flex items-start gap-3 p-3 bg-muted rounded-xl cursor-pointer">
@@ -420,9 +419,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
onChange={(e) => setConditions((prev) => ({ ...prev, neverShare: e.target.checked }))}
className="mt-1 w-5 h-5 accent-primary"
/>
<span className="text-sm">
Ez fêm dikim ku ez nikarim ev peyvan bi kesî re parve bikim
</span>
<span className="text-sm">{t('walletCreate.conditionNeverShare')}</span>
</label>
<label className="flex items-start gap-3 p-3 bg-muted rounded-xl cursor-pointer">
@@ -432,10 +429,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
onChange={(e) => setConditions((prev) => ({ ...prev, lossRisk: e.target.checked }))}
className="mt-1 w-5 h-5 accent-primary"
/>
<span className="text-sm">
Ez fêm dikim ku eger van peyvan winda bikim ez nikarim gihîştina wallet&apos;ê xwe
bistînim
</span>
<span className="text-sm">{t('walletCreate.conditionLossRisk')}</span>
</label>
</div>
@@ -450,7 +444,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
disabled={!allConditionsChecked}
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{allConditionsChecked ? 'Berdewam' : 'Hemû şertan bipejirînin'}
{allConditionsChecked ? t('common.continue') : t('walletCreate.acceptAllConditions')}
{allConditionsChecked && <ArrowRight className="w-4 h-4" />}
</button>
</div>
@@ -461,24 +455,26 @@ export function WalletCreate({ onComplete, onBack }: Props) {
return (
<div className="p-4 space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold">Peyvan Verast Bike</h2>
<h2 className="text-lg font-semibold">{t('walletCreate.verifyWords')}</h2>
<button
onClick={handleReset}
className="flex items-center gap-1 text-sm text-muted-foreground"
>
<RotateCcw className="w-4 h-4" />
<span>Reset</span>
<span>{t('walletCreate.reset')}</span>
</button>
</div>
<p className="text-muted-foreground text-sm text-center">
Ji kerema xwe peyvan bi rêza rast bixin nav qutîkê
{t('walletCreate.verifyDescription')}
</p>
{/* Destination area - where user builds the correct order */}
<div className="min-h-[120px] p-4 bg-muted/50 border-2 border-dashed border-border rounded-xl">
{destinationWords.length === 0 ? (
<p className="text-center text-muted-foreground text-sm">Peyvan li vir bixin...</p>
<p className="text-center text-muted-foreground text-sm">
{t('walletCreate.dropWordsHere')}
</p>
) : (
<div className="flex flex-wrap gap-2">
{destinationWords.map((word, idx) => (
@@ -525,10 +521,10 @@ export function WalletCreate({ onComplete, onBack }: Props) {
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50"
>
{isLoading
? 'Tê tomarkirin...'
? t('walletCreate.saving')
: canVerify
? 'Verast Bike'
: `${destinationWords.length}/12 peyv`}
? t('walletCreate.verify')
: t('walletCreate.wordsCount', { count: destinationWords.length })}
</button>
</div>
);
@@ -542,12 +538,12 @@ export function WalletCreate({ onComplete, onBack }: Props) {
</div>
<div>
<h2 className="text-xl font-semibold mb-2">Wallet Hat Çêkirin!</h2>
<p className="text-muted-foreground text-sm">Wallet&apos;ê te amade ye</p>
<h2 className="text-xl font-semibold mb-2">{t('walletCreate.walletCreated')}</h2>
<p className="text-muted-foreground text-sm">{t('walletCreate.walletReady')}</p>
</div>
<div className="p-4 bg-muted rounded-xl">
<p className="text-xs text-muted-foreground mb-1">Navnîşana te</p>
<p className="text-xs text-muted-foreground mb-1">{t('walletCreate.yourAddress')}</p>
<p className="font-mono text-sm break-all">{address}</p>
</div>
@@ -555,7 +551,7 @@ export function WalletCreate({ onComplete, onBack }: Props) {
onClick={onComplete}
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold"
>
Dest Bike
{t('walletCreate.getStarted')}
</button>
</div>
);
+31 -25
View File
@@ -8,6 +8,7 @@ import { Eye, EyeOff, ArrowLeft, ArrowRight, Check, AlertTriangle } from 'lucide
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { validatePassword } from '@/lib/crypto';
import { useTranslation } from '@/i18n';
interface Props {
onComplete: () => void;
@@ -17,6 +18,7 @@ interface Props {
export function WalletImport({ onComplete, onBack }: Props) {
const { importWallet } = useWallet();
const { hapticImpact, hapticNotification } = useTelegram();
const { t } = useTranslation();
const [mnemonic, setMnemonic] = useState('');
const [password, setPassword] = useState('');
@@ -49,18 +51,18 @@ export function WalletImport({ onComplete, onBack }: Props) {
// Validate mnemonic
const words = mnemonic.trim().split(/\s+/);
if (words.length !== 12 && words.length !== 24) {
setError('Seed phrase divê 12 an 24 peyv be');
setError(t('walletImport.seedPhraseInvalid'));
return;
}
// Validate password using crypto.ts rules
const passwordValidation = validatePassword(password);
if (!passwordValidation.valid) {
setError(passwordValidation.message || 'Şîfre (password) ne derbasdar e');
setError(passwordValidation.message || t('walletImport.passwordInvalid'));
return;
}
if (password !== confirmPassword) {
setError('Şîfre (password) hev nagirin');
setError(t('walletImport.passwordsMismatch'));
return;
}
@@ -72,7 +74,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
hapticNotification('success');
onComplete();
} catch (err) {
setError(err instanceof Error ? err.message : 'Import neserketî');
setError(err instanceof Error ? err.message : t('walletImport.importFailed'));
hapticNotification('error');
} finally {
setIsLoading(false);
@@ -83,24 +85,24 @@ export function WalletImport({ onComplete, onBack }: Props) {
<div className="p-4 space-y-6">
<button onClick={onBack} className="flex items-center gap-2 text-muted-foreground">
<ArrowLeft className="w-4 h-4" />
<span>Paş</span>
<span>{t('common.back')}</span>
</button>
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">Wallet Import Bike</h2>
<p className="text-muted-foreground text-sm">
Seed phrase&apos;ê wallet&apos;ê xwe heyî binivîse
</p>
<h2 className="text-xl font-semibold mb-2">{t('walletImport.title')}</h2>
<p className="text-muted-foreground text-sm">{t('walletImport.description')}</p>
</div>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Seed Phrase (12 an 24 peyv)</label>
<label className="text-sm text-muted-foreground">
{t('walletImport.seedPhraseLabel')}
</label>
<textarea
value={mnemonic}
onChange={(e) => setMnemonic(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl resize-none h-28 font-mono text-sm"
placeholder="Peyvên xwe bi valahî cuda binivîse..."
placeholder={t('walletImport.seedPhrasePlaceholder')}
/>
<p className="text-xs text-muted-foreground">
{mnemonic.trim().split(/\s+/).filter(Boolean).length} / 12 peyv
@@ -108,14 +110,14 @@ export function WalletImport({ onComplete, onBack }: Props) {
</div>
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfreya (New Password)</label>
<label className="text-sm text-muted-foreground">{t('walletImport.newPassword')}</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl pr-12"
placeholder="Herî kêm 12 tîp (min 12 characters)"
placeholder={t('walletCreate.passwordPlaceholder')}
/>
<button
type="button"
@@ -128,20 +130,24 @@ export function WalletImport({ onComplete, onBack }: Props) {
</div>
<div className="space-y-2">
<label className="text-sm text-muted-foreground">Şîfre Dubare (Confirm Password)</label>
<label className="text-sm text-muted-foreground">
{t('walletCreate.confirmPasswordLabel')}
</label>
<input
type={showPassword ? 'text' : 'password'}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-4 py-3 bg-muted rounded-xl"
placeholder="Şîfre dubare binivîse (confirm password)"
placeholder={t('walletCreate.confirmPasswordPlaceholder')}
/>
</div>
{/* Real-time password strength indicator */}
{password.length > 0 && (
<div className="p-3 bg-muted/50 rounded-xl space-y-2">
<p className="text-xs text-muted-foreground font-medium">Şertên Şîfre (Password):</p>
<p className="text-xs text-muted-foreground font-medium">
{t('walletCreate.passwordRequirements')}
</p>
<div className="grid grid-cols-1 gap-1.5 text-xs">
<div
className={`flex items-center gap-2 ${passwordRules.minLength ? 'text-green-400' : 'text-red-400'}`}
@@ -151,7 +157,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 12 tîp (min 12 characters)</span>
<span>{t('walletCreate.ruleMinLength')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasLowercase ? 'text-green-400' : 'text-red-400'}`}
@@ -161,7 +167,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 tîpa biçûk (a-z)</span>
<span>{t('walletCreate.ruleLowercase')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasUppercase ? 'text-green-400' : 'text-red-400'}`}
@@ -171,7 +177,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 tîpa mezin (A-Z)</span>
<span>{t('walletCreate.ruleUppercase')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasNumber ? 'text-green-400' : 'text-red-400'}`}
@@ -181,7 +187,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 hejmar (0-9)</span>
<span>{t('walletCreate.ruleNumber')}</span>
</div>
<div
className={`flex items-center gap-2 ${passwordRules.hasSpecialChar ? 'text-green-400' : 'text-red-400'}`}
@@ -191,7 +197,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Herî kêm 1 sembola taybetî (!@#$%...)</span>
<span>{t('walletCreate.ruleSpecialChar')}</span>
</div>
{confirmPassword.length > 0 && (
<div
@@ -202,7 +208,7 @@ export function WalletImport({ onComplete, onBack }: Props) {
) : (
<AlertTriangle className="w-3 h-3" />
)}
<span>Şîfre (password) hev digirin</span>
<span>{t('walletCreate.rulePasswordsMatch')}</span>
</div>
)}
</div>
@@ -221,10 +227,10 @@ export function WalletImport({ onComplete, onBack }: Props) {
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{isLoading
? 'Tê import kirin...'
? t('walletImport.importing')
: allPasswordRulesPass
? 'Import Bike'
: 'Şertên şîfre (password) bicîh bînin'}
? t('walletImport.importButton')
: t('walletCreate.meetPasswordRequirements')}
{!isLoading && allPasswordRulesPass && <ArrowRight className="w-4 h-4" />}
</button>
</div>
+9 -8
View File
@@ -4,6 +4,7 @@
*/
import { Wallet, Plus, Download } from 'lucide-react';
import { useTranslation } from '@/i18n';
interface Props {
onCreate: () => void;
@@ -11,6 +12,8 @@ interface Props {
}
export function WalletSetup({ onCreate, onImport }: Props) {
const { t } = useTranslation();
return (
<div className="p-4 space-y-8">
<div className="text-center pt-8">
@@ -18,7 +21,7 @@ export function WalletSetup({ onCreate, onImport }: Props) {
<Wallet className="w-10 h-10 text-primary" />
</div>
<h1 className="text-2xl font-bold mb-2">Pezkuwi Wallet</h1>
<p className="text-muted-foreground">Berîka fermî ya Pezkuwichain</p>
<p className="text-muted-foreground">{t('walletSetup.officialWallet')}</p>
</div>
<div className="space-y-3">
@@ -30,8 +33,8 @@ export function WalletSetup({ onCreate, onImport }: Props) {
<Plus className="w-6 h-6" />
</div>
<div className="text-left">
<p className="font-semibold">Wallet Çêbike</p>
<p className="text-sm opacity-80">Wallet&apos;ekî bi seed phrase çêbike</p>
<p className="font-semibold">{t('walletSetup.createNew')}</p>
<p className="text-sm opacity-80">{t('walletSetup.createNewDesc')}</p>
</div>
</button>
@@ -43,16 +46,14 @@ export function WalletSetup({ onCreate, onImport }: Props) {
<Download className="w-6 h-6 text-primary" />
</div>
<div className="text-left">
<p className="font-semibold">Wallet Import Bike</p>
<p className="text-sm text-muted-foreground">
Seed phrase&apos;ê xwe heyî bi kar bîne
</p>
<p className="font-semibold">{t('walletSetup.importWallet')}</p>
<p className="text-sm text-muted-foreground">{t('walletSetup.importWalletDesc')}</p>
</div>
</button>
</div>
<p className="text-center text-xs text-muted-foreground px-4">
Wallet&apos;ê te bi ewlehî li cîhaza te hilanîn. Em tu carî gihîştina mifteyên te tune.
{t('walletSetup.securityNote')}
</p>
</div>
);
+131
View File
@@ -0,0 +1,131 @@
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react';
import type { Translations, LanguageCode } from './types';
import { RTL_LANGUAGES } from './types';
import krd from './translations/krd';
import en from './translations/en';
import tr from './translations/tr';
import ckb from './translations/ckb';
import fa from './translations/fa';
import ar from './translations/ar';
const translations: Record<LanguageCode, Translations> = {
krd,
en,
tr,
ckb,
fa,
ar,
};
const VALID_LANGS: LanguageCode[] = ['krd', 'en', 'tr', 'ckb', 'fa', 'ar'];
const DEFAULT_LANG: LanguageCode = 'krd';
/**
* Detect language from URL path.
* e.g. /krd/... -> krd, /en/... -> en, / -> krd (default)
*/
function detectLanguageFromURL(): LanguageCode {
const path = window.location.pathname;
const firstSegment = path.split('/').filter(Boolean)[0];
if (firstSegment && VALID_LANGS.includes(firstSegment as LanguageCode)) {
return firstSegment as LanguageCode;
}
return DEFAULT_LANG;
}
/**
* Get a nested value from an object using dot notation.
* e.g. getNestedValue(obj, 'nav.forum') -> obj.nav.forum
*/
function getNestedValue(obj: Record<string, unknown>, path: string): string | undefined {
const keys = path.split('.');
let current: unknown = obj;
for (const key of keys) {
if (current == null || typeof current !== 'object') return undefined;
current = (current as Record<string, unknown>)[key];
}
return typeof current === 'string' ? current : undefined;
}
interface LanguageContextType {
lang: LanguageCode;
setLang: (lang: LanguageCode) => void;
t: (key: string, params?: Record<string, string | number>) => string;
isRTL: boolean;
}
const LanguageContext = createContext<LanguageContextType | null>(null);
interface LanguageProviderProps {
children: ReactNode;
}
export function LanguageProvider({ children }: LanguageProviderProps) {
const [lang, setLangState] = useState<LanguageCode>(detectLanguageFromURL);
const isRTL = RTL_LANGUAGES.includes(lang);
// Update document direction and lang attribute when language changes
useEffect(() => {
document.documentElement.lang = lang === 'krd' ? 'ku' : lang;
document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
}, [lang, isRTL]);
const setLang = useCallback((newLang: LanguageCode) => {
setLangState(newLang);
// Update URL without reload
const currentPath = window.location.pathname;
const segments = currentPath.split('/').filter(Boolean);
// Remove old lang prefix if present
if (segments.length > 0 && VALID_LANGS.includes(segments[0] as LanguageCode)) {
segments.shift();
}
const newPath = `/${newLang}${segments.length > 0 ? '/' + segments.join('/') : ''}`;
window.history.replaceState(null, '', newPath);
}, []);
const t = useCallback(
(key: string, params?: Record<string, string | number>): string => {
// Try current language first
let value = getNestedValue(translations[lang] as unknown as Record<string, unknown>, key);
// Fallback to Kurdish
if (value === undefined && lang !== DEFAULT_LANG) {
value = getNestedValue(
translations[DEFAULT_LANG] as unknown as Record<string, unknown>,
key
);
}
// If still not found, return the key itself
if (value === undefined) return key;
// Replace params like {time}, {count}, {amount}, {rating}
if (params) {
let result = value;
for (const [paramKey, paramValue] of Object.entries(params)) {
result = result.replace(`{${paramKey}}`, String(paramValue));
}
return result;
}
return value;
},
[lang]
);
return (
<LanguageContext.Provider value={{ lang, setLang, t, isRTL }}>
{children}
</LanguageContext.Provider>
);
}
export function useTranslation() {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useTranslation must be used within a LanguageProvider');
}
return context;
}
export type { LanguageCode, Translations };
export { VALID_LANGS, DEFAULT_LANG, RTL_LANGUAGES, LANGUAGE_NAMES } from './types';
+304
View File
@@ -0,0 +1,304 @@
import type { Translations } from '../types';
const ar: Translations = {
nav: {
announcements: 'الأخبار',
forum: 'المنتدى',
rewards: 'المكافآت',
p2p: 'P2P',
wallet: 'المحفظة',
},
common: {
back: 'رجوع',
cancel: 'إلغاء',
continue: 'متابعة',
close: 'إغلاق',
copy: 'نسخ',
copied: 'تم النسخ!',
share: 'مشاركة',
refresh: 'تحديث',
retry: 'حاول مجددًا',
loading: 'جارٍ التحميل...',
error: 'خطأ',
anonymous: 'مجهول',
pinned: 'مثبّت',
locked: 'مقفل',
trending: 'رائج',
},
announcements: {
title: 'الإعلانات',
readMore: 'اقرأ المزيد',
reactionAuthRequired: 'يجب أن تكون في تيليجرام للتصويت',
},
forum: {
title: 'المنتدى',
newTopic: 'موضوع جديد',
submitting: 'جارٍ الإرسال...',
publish: 'نشر',
category: 'الفئة',
topicTitle: 'العنوان',
topicTitlePlaceholder: 'عنوان الموضوع...',
content: 'المحتوى',
contentPlaceholder: 'اكتب محتوى الموضوع...',
tagsLabel: 'الوسوم (افصل بينها بفاصلة)',
tagsPlaceholder: 'blockchain, kurd, pez...',
searchPlaceholder: 'ابحث في المواضيع...',
sortRecent: 'جديد',
sortPopular: 'شائع',
sortReplies: 'الردود',
sortViews: 'المشاهدات',
all: 'الكل',
replies: 'الردود',
noRepliesYet: 'لا توجد ردود بعد',
beFirstToReply: 'كن أول من يردّ!',
replyPlaceholder: 'اكتب ردّك...',
noTopicsFound: 'لم يتم العثور على مواضيع',
changeFilters: 'غيّر عوامل التصفية',
createNewTopic: 'إنشاء موضوع جديد',
loginToVote: 'سجّل الدخول للتصويت',
voteError: 'خطأ في التصويت',
writeReply: 'يرجى كتابة ردّك',
topicLocked: 'هذا الموضوع مقفل',
replyError: 'خطأ في إرسال الرد',
fillAllFields: 'يرجى ملء جميع الحقول',
topicCreated: 'تم إنشاء الموضوع!',
topicCreateError: 'خطأ في إنشاء الموضوع',
},
rewards: {
title: 'المكافآت',
subtitle: 'المكافآت - نظام الإحالة',
connectWalletFirst: 'لعرض إحالاتك ومكافآتك، قم بربط محفظتك أولاً.',
overview: 'نظرة عامة',
referral: 'الإحالة',
scores: 'النقاط',
referralScore: 'نقاط الإحالة',
maxScore: 'الحد الأقصى: 500',
referMoreTitle: 'أحِل أكثر، واكسب أكثر!',
referMoreDescription:
'مقابل كل شخص تدعوه، تحصل على HEZ و PEZ كمكافآت. المزيد من الإحالات = المزيد من المكافآت!',
kycApproved: 'تم اعتماد KYC',
referrer: 'المُحيل',
none: 'لا يوجد',
invitedMe: 'دعاني',
pendingReferral: 'إحالة معلّقة',
completeKyc: 'أكمل KYC للموافقة على الإحالة من',
inviteFriends: 'ادعُ أصدقاءك',
yourLink: 'رابطك',
copySuccess: 'تم النسخ!',
copyLink: 'نسخ',
shareLink: 'مشاركة',
scoreSystem: 'نظام النقاط',
activeStatus: 'حالة النشاط',
timeRemaining: 'الوقت: {time} متبقٍ',
active: 'نشط',
inactive: 'غير نشط',
activeDescription: 'انقر مرة واحدة كل 24 ساعة لتبقى نشطًا وتكسب المزيد من المكافآت!',
youAreActive: 'أنت نشط!',
iAmActive: 'أنا نشط!',
activatedAlert: 'أنت الآن نشط! انقر مجددًا بعد 24 ساعة.',
shareText: 'Pezkuwichain - الدولة الرقمية لكردستان! انضم إلينا عبر رابطي:',
copyAlert: 'تم النسخ',
referralCount: '{count} إحالة (معتمدة KYC)',
noReferrals: 'ليس لديك إحالات بعد',
shareYourLink: 'شارك رابطك!',
trustScore: 'نقاط الثقة',
rank: 'الرتبة: {rating}',
citizen: 'مواطن',
staking: 'التخزين',
stakingNotStarted: 'لم يبدأ بعد',
stakingCountedInTrust: 'يُحتسب في نقاط الثقة',
people: '{count} شخص',
tiki: 'تيكي',
nftRole: 'دور NFT',
education: 'التعليم',
reading: 'القراءة',
stakingRewards: 'مكافآت التخزين',
totalRewards: 'إجمالي المكافآت المستلمة',
recentRewards: 'المكافآت الأخيرة',
noRewardsYet: 'لا توجد مكافآت مسجّلة بعد',
scoreFormula: 'معادلة النقاط',
stakingZeroWarning: 'إذا كان التخزين 0، فنقاط الثقة أيضًا 0. قم بالتخزين أولاً!',
refreshScores: 'تحديث النقاط',
points: 'نقاط',
},
wallet: {
title: 'المحفظة',
authFailed: 'فشل المصادقة',
authDescription: 'يرجى التأكد من فتح هذا التطبيق داخل تيليجرام',
retry: 'حاول مجددًا',
},
walletSetup: {
officialWallet: 'محفظة Pezkuwichain الرسمية',
createNew: 'إنشاء محفظة جديدة',
createNewDesc: 'إنشاء محفظة جديدة بعبارة الاسترداد',
importWallet: 'استيراد محفظة',
importWalletDesc: 'استخدم عبارة الاسترداد الموجودة لديك',
securityNote: 'محفظتك مخزّنة بأمان على جهازك. لا نملك أبدًا إمكانية الوصول إلى مفاتيحك.',
},
walletCreate: {
setPassword: 'تعيين كلمة المرور',
passwordDescription: 'ستُستخدم كلمة المرور هذه لفتح محفظتك',
passwordLabel: 'كلمة المرور',
passwordPlaceholder: '12 حرفًا على الأقل',
confirmPasswordLabel: 'تأكيد كلمة المرور',
confirmPasswordPlaceholder: 'أكّد كلمة المرور',
passwordRequirements: 'متطلبات كلمة المرور:',
ruleMinLength: '12 حرفًا على الأقل',
ruleLowercase: 'حرف صغير واحد على الأقل (a-z)',
ruleUppercase: 'حرف كبير واحد على الأقل (A-Z)',
ruleNumber: 'رقم واحد على الأقل (0-9)',
ruleSpecialChar: 'رمز خاص واحد على الأقل (!@#$%...)',
rulePasswordsMatch: 'كلمتا المرور متطابقتان',
walletServiceNotReady: 'خدمة المحفظة غير جاهزة. يرجى الانتظار.',
passwordRequirementsNotMet: 'يرجى استيفاء جميع متطلبات كلمة المرور',
walletCreationFailed: 'فشل إنشاء المحفظة. يرجى المحاولة مجددًا',
preparing: 'جارٍ التحضير...',
creating: 'جارٍ الإنشاء...',
meetPasswordRequirements: 'استوفِ متطلبات كلمة المرور',
backupTitle: 'نسخ عبارة الاسترداد احتياطيًا',
backupDescription: 'هذه الكلمات الـ 12 هي محفظتك. اكتبها في مكان آمن!',
backupWarning:
'مهم: تُعرض هذه الكلمات مرة واحدة فقط. إذا فقدتها، لن تتمكن من الوصول إلى محفظتك.',
copiedMnemonic: 'تم النسخ!',
copyMnemonic: 'نسخ',
conditionWrittenDown: 'لقد كتبت هذه الكلمات الـ 12 في مكان آمن',
conditionNeverShare: 'أفهم أنه يجب ألّا أشارك هذه الكلمات مع أي شخص',
conditionLossRisk: 'أفهم أنه إذا فقدت هذه الكلمات فلن أتمكن من الوصول إلى محفظتي',
acceptAllConditions: 'قبول جميع الشروط',
verifyWords: 'التحقق من الكلمات',
reset: 'إعادة تعيين',
verifyDescription: 'يرجى ترتيب الكلمات بالترتيب الصحيح',
dropWordsHere: 'ضع الكلمات هنا...',
wrongOrder: 'ترتيب الكلمات خاطئ. يرجى المحاولة مجددًا',
saving: 'جارٍ الحفظ...',
verify: 'تحقّق',
wordsCount: '{count}/12 كلمة',
walletCreated: 'تم إنشاء المحفظة!',
walletReady: 'محفظتك جاهزة',
yourAddress: 'عنوانك',
getStarted: 'ابدأ الآن',
},
walletImport: {
title: 'استيراد محفظة',
description: 'أدخل عبارة الاسترداد الخاصة بمحفظتك الحالية',
seedPhraseLabel: 'عبارة الاسترداد (12 أو 24 كلمة)',
seedPhrasePlaceholder: 'أدخل كلماتك مفصولة بمسافات...',
wordsCount: '{count} / 12 كلمة',
newPassword: 'كلمة مرور جديدة',
seedPhraseInvalid: 'يجب أن تكون عبارة الاسترداد 12 أو 24 كلمة',
passwordInvalid: 'كلمة المرور غير صالحة',
passwordsMismatch: 'كلمتا المرور غير متطابقتين',
importFailed: 'فشل الاستيراد',
importing: 'جارٍ الاستيراد...',
importButton: 'استيراد',
},
walletConnect: {
deleteTitle: 'حذف المحفظة؟',
deleteDescription:
'لا يمكن التراجع عن هذا الإجراء. إذا لم تكن لديك عبارة الاسترداد، فلن تتمكن من الوصول إلى محفظتك.',
deleteButton: 'حذف',
openWallet: 'فتح المحفظة',
passwordLabel: 'كلمة المرور',
passwordPlaceholder: 'أدخل كلمة المرور',
enterPassword: 'أدخل كلمة المرور',
wrongPassword: 'كلمة مرور خاطئة',
connecting: 'جارٍ الفتح...',
connect: 'اتصال',
deleteWalletLink: 'حذف المحفظة',
},
deposit: {
title: 'إيداع USDT',
subtitle: 'لـ wUSDT على Asset Hub',
depositHistory: 'سجل الإيداعات',
noDeposits: 'لا توجد إيداعات بعد',
goBack: 'رجوع',
selectNetwork: 'اختر الشبكة',
recommended: 'موصى به',
warning: 'تحذير!',
example: 'مثال: أرسل 10 USDT \u2192 تستلم 7 wUSDT (رسوم $3)',
acceptTrc20: 'أوافق، أرسل عبر TRC20',
important: 'مهم!',
minimum: 'الحد الأدنى: {amount} USDT',
memoRequired: 'اكتب الرمز الخاص بك في حقل المذكرة/التعليق',
onlyUsdt: 'أرسل USDT فقط، العملات الأخرى ستُفقد',
trc20Fee: 'سيتم خصم رسوم $3 من المبلغ الخاص بك',
depositAddress: 'عنوان الإيداع',
notAvailable: 'غير متاح',
memoLabel: 'المذكرة / التعليق (مطلوب)',
memoWarning: 'اكتب هذا الرمز في المذكرة، وإلا لن يتم التعرف على إيداعك!',
uniqueAddress: 'هذا العنوان خاص بك. لا حاجة لمذكرة.',
howToDeposit: 'كيفية الإيداع؟',
stepCopyAddress: 'انسخ العنوان',
stepCopyMemo: 'انسخ رمز المذكرة',
stepOpenTon: 'افتح محفظة تيليجرام أو محفظتك',
stepOpenPolkadot: 'افتح محفظة Polkadot الخاصة بك',
stepOpenTrc20: 'افتح TronLink أو محفظتك',
stepSendUsdt: 'أرسل USDT إلى العنوان أعلاه',
stepReceive: 'سيصل wUSDT إلى حسابك خلال دقائق قليلة',
processingTime: 'وقت المعالجة: ~1-5 دقائق',
copyFailed: 'فشل النسخ',
statusPending: 'قيد الانتظار',
statusConfirming: 'قيد التأكيد',
statusCompleted: 'مكتمل',
statusFailed: 'فشل',
statusExpired: 'منتهي الصلاحية',
viewTx: 'عرض المعاملة',
trc20FeeWarning: 'رسوم شبكة TRC20 حوالي $3. نوصي بشبكة TON أو Polkadot.',
},
p2p: {
title: 'تبادل P2P',
subtitle: 'تداول العملات الرقمية بين الأفراد',
firstTime: 'أول مرة تستخدم P2P؟',
steps: [
'انقر على الزر أدناه لفتح تطبيق الويب',
'أنشئ حسابًا أو سجّل الدخول',
'أكمل عملية إعداد P2P',
'بعد الإعداد، يمكنك الوصول إلى P2P مباشرة',
],
note: 'سيُفتح تطبيق الويب في نافذة جديدة. أكمل عملية التسجيل هناك.',
button: 'فتح منصة P2P',
},
update: {
newVersion: 'يتوفر إصدار جديد!',
description: 'حدّث للحصول على ميزات جديدة وإصلاحات أمنية.',
later: 'لاحقًا',
updateNow: 'تحديث',
},
social: {
followUs: 'تابعنا',
stayConnected: 'ابقَ على تواصل واحصل على آخر الأخبار!',
instagram: 'صور وقصص',
tiktok: 'فيديوهات قصيرة',
snapchat: 'أرسل لنا سناب!',
telegram: 'القناة الرسمية',
twitter: 'أخبار يومية',
youtube: 'فيديوهاتنا',
facebook: 'الصفحة الرسمية',
discord: 'مجتمعنا',
},
errorBoundary: {
title: 'حدث خطأ ما',
description: 'عذرًا، حدث خطأ تقني. يرجى المحاولة مجددًا.',
retry: 'حاول مجددًا',
},
loadingScreen: {
loading: 'جارٍ التحميل...',
},
};
export default ar;
+306
View File
@@ -0,0 +1,306 @@
import type { Translations } from '../types';
const ckb: Translations = {
nav: {
announcements: 'هەواڵەکان',
forum: 'فۆڕەم',
rewards: 'خەڵاتەکان',
p2p: 'P2P',
wallet: 'جزدان',
},
common: {
back: 'گەڕانەوە',
cancel: 'هەڵوەشاندنەوە',
continue: 'بەردەوامبوون',
close: 'داخستن',
copy: 'لەبەرگرتنەوە',
copied: 'لەبەرگیرا!',
share: 'هاوبەشکردن',
refresh: 'نوێکردنەوە',
retry: 'دووبارە هەوڵبدەرەوە',
loading: 'بارکردن...',
error: 'هەڵە',
anonymous: 'بێناو',
pinned: 'سنجەکراو',
locked: 'قفڵکراو',
trending: 'بەرزەبایەخ',
},
announcements: {
title: 'ڕاگەیاندنەکان',
readMore: 'زیاتر بخوێنەرەوە',
reactionAuthRequired: 'بۆ دەنگدان دەبێت لە تێلێگرام بیت',
},
forum: {
title: 'فۆڕەم',
newTopic: 'بابەتی نوێ',
submitting: 'ناردن...',
publish: 'بڵاوکردنەوە',
category: 'پۆل',
topicTitle: 'ناونیشان',
topicTitlePlaceholder: 'ناونیشانی بابەت...',
content: 'ناوەڕۆک',
contentPlaceholder: 'ناوەڕۆکی بابەتەکە بنووسە...',
tagsLabel: 'تاگەکان (بە کۆما جیابکەرەوە)',
tagsPlaceholder: 'بلۆکچەین، کورد، پێز...',
searchPlaceholder: 'گەڕان لە بابەتەکان...',
sortRecent: 'نوێ',
sortPopular: 'بەناوبانگ',
sortReplies: 'وەڵامەکان',
sortViews: 'بینینەکان',
all: 'هەموو',
replies: 'وەڵامەکان',
noRepliesYet: 'هێشتا هیچ وەڵامێک نییە',
beFirstToReply: 'یەکەم کەس بە بۆ وەڵامدانەوە!',
replyPlaceholder: 'وەڵامەکەت بنووسە...',
noTopicsFound: 'هیچ بابەتێک نەدۆزرایەوە',
changeFilters: 'فلتەرەکانت بگۆڕە',
createNewTopic: 'بابەتی نوێ دروستبکە',
loginToVote: 'بۆ دەنگدان چوونەژوورەوە بکە',
voteError: 'هەڵە لە دەنگدان',
writeReply: 'تکایە وەڵامەکەت بنووسە',
topicLocked: 'ئەم بابەتە قفڵکراوە',
replyError: 'هەڵە لە ناردنی وەڵام',
fillAllFields: 'تکایە هەموو خانەکان پڕبکەرەوە',
topicCreated: 'بابەتەکە دروستکرا!',
topicCreateError: 'هەڵە لە دروستکردنی بابەت',
},
rewards: {
title: 'خەڵاتەکان',
subtitle: 'خەڵاتەکان - سیستەمی بانگهێشتکردن',
connectWalletFirst: 'بۆ بینینی بانگهێشتکراوەکانت و خەڵاتەکان، سەرەتا جزدانەکەت پەیوەست بکە.',
overview: 'پوختە',
referral: 'بانگهێشتکردن',
scores: 'خاڵەکان',
referralScore: 'خاڵی بانگهێشتکردن',
maxScore: 'زۆرترین خاڵ: ٥٠٠',
referMoreTitle: 'زیاتر بانگهێشت بکە، زیاتر قازانج بکە!',
referMoreDescription:
'بۆ هەر کەسێک کە دەیهێنیت، HEZ و PEZ وەک خەڵات وەردەگریت. زیاتر بانگهێشتکردن = زیاتر خەڵات!',
kycApproved: 'KYC پەسەندکراو',
referrer: 'بانگهێشتکەر',
none: 'هیچ',
invitedMe: 'بانگهێشتی منی کرد',
pendingReferral: 'بانگهێشتی چاوەڕوان',
completeKyc: 'KYC تەواو بکە بۆ پەسەندکردنی بانگهێشتکردن لەلایەن',
inviteFriends: 'هاوڕێکانت بانگهێشت بکە',
yourLink: 'لینکەکەت',
copySuccess: 'لەبەرگیرا!',
copyLink: 'لەبەرگرتنەوە',
shareLink: 'هاوبەشکردن',
scoreSystem: 'سیستەمی خاڵ',
activeStatus: 'دۆخی چالاکی',
timeRemaining: 'کات: {time} ماوە',
active: 'چالاک',
inactive: 'ناچالاک',
activeDescription:
'هەر ٢٤ کاتژمێرێک جارێک کلیک بکە بۆ ئەوەی چالاک بمێنیت و زیاتر خەڵات بەدەستبهێنیت!',
youAreActive: 'تۆ چالاکیت!',
iAmActive: 'من چالاکم!',
activatedAlert: 'ئێستا چالاکیت! دوای ٢٤ کاتژمێر دووبارە کلیک بکە.',
shareText: 'پێزکوێچەین - دەوڵەتی دیجیتاڵی کوردستان! لە ڕێگەی لینکەکەمەوە پەیوەستمان ببە:',
copyAlert: 'لەبەرگیرا',
referralCount: '{count} بانگهێشتکراو (KYC پەسەندکراو)',
noReferrals: 'هێشتا هیچ بانگهێشتکراوێکت نییە',
shareYourLink: 'لینکەکەت هاوبەش بکە!',
trustScore: 'خاڵی متمانە',
rank: 'پلە: {rating}',
citizen: 'هاوڵاتی',
staking: 'ستەیکینگ',
stakingNotStarted: 'دەستپێنەکراوە',
stakingCountedInTrust: 'لە خاڵی متمانە ژمێردراوە',
people: '{count} کەس',
tiki: 'تیکی',
nftRole: 'ڕۆڵی NFT',
education: 'پەروەردە',
reading: 'خوێندنەوە',
stakingRewards: 'خەڵاتەکانی ستەیکینگ',
totalRewards: 'کۆی خەڵاتە وەرگیراوەکان',
recentRewards: 'خەڵاتە تازەکان',
noRewardsYet: 'هێشتا هیچ خەڵاتێک تۆمار نەکراوە',
scoreFormula: 'فۆرمولای خاڵ',
stakingZeroWarning: 'ئەگەر ستەیکینگ ٠ بێت، خاڵی متمانەش ٠ دەبێت. سەرەتا ستەیک بکە!',
refreshScores: 'نوێکردنەوەی خاڵەکان',
points: 'خاڵ',
},
wallet: {
title: 'جزدان',
authFailed: 'ناسینەوە سەرکەوتوو نەبوو',
authDescription: 'تکایە دڵنیابەرەوە کە ئەم ئەپەت لەناو تێلێگرام کردووەتەوە',
retry: 'دووبارە هەوڵبدەرەوە',
},
walletSetup: {
officialWallet: 'جزدانی فەرمی پێزکوێچەین',
createNew: 'جزدانی نوێ دروستبکە',
createNewDesc: 'جزدانی نوێ بە وشەی نهێنی دروستبکە',
importWallet: 'هاوردەکردنی جزدان',
importWalletDesc: 'وشەی نهێنی ئامادەکەت بەکاربهێنە',
securityNote:
'جزدانەکەت بە پاراستنەوە لە ئامێرەکەتدا هەڵدەگیرێت. ئێمە هەرگیز دەسمان بە کلیلەکانت ناگات.',
},
walletCreate: {
setPassword: 'وشەی نهێنی دابنێ',
passwordDescription: 'ئەم وشەی نهێنییە بۆ کردنەوەی جزدانەکەت بەکاردێت',
passwordLabel: 'وشەی نهێنی',
passwordPlaceholder: 'لانیکەم ١٢ پیت',
confirmPasswordLabel: 'دووپاتکردنەوەی وشەی نهێنی',
confirmPasswordPlaceholder: 'وشەی نهێنی دووبارە بنووسە',
passwordRequirements: 'مەرجەکانی وشەی نهێنی:',
ruleMinLength: 'لانیکەم ١٢ پیت',
ruleLowercase: 'لانیکەم ١ پیتی بچووک (a-z)',
ruleUppercase: 'لانیکەم ١ پیتی گەورە (A-Z)',
ruleNumber: 'لانیکەم ١ ژمارە (0-9)',
ruleSpecialChar: 'لانیکەم ١ پیتی تایبەت (!@#$%...)',
rulePasswordsMatch: 'وشەی نهێنییەکان وەکیەکن',
walletServiceNotReady: 'خزمەتگوزاری جزدان ئامادە نییە. تکایە چاوەڕوان بە.',
passwordRequirementsNotMet: 'تکایە هەموو مەرجەکانی وشەی نهێنی جێبەجێ بکە',
walletCreationFailed: 'دروستکردنی جزدان سەرکەوتوو نەبوو. تکایە دووبارە هەوڵبدەرەوە',
preparing: 'ئامادەکردن...',
creating: 'دروستکردن...',
meetPasswordRequirements: 'مەرجەکانی وشەی نهێنی جێبەجێ بکە',
backupTitle: 'پاشەکەوتکردنی وشەی نهێنی',
backupDescription: 'ئەم ١٢ وشەیە جزدانەکەتن. لە شوێنێکی سەلامەتدا بینووسە!',
backupWarning:
'گرنگ: ئەم وشانە تەنها یەک جار نیشان دەدرێن. ئەگەر لەدەست بدەیت، ناتوانیت دەستت بە جزدانەکەت بگات.',
copiedMnemonic: 'لەبەرگیرا!',
copyMnemonic: 'لەبەرگرتنەوە',
conditionWrittenDown: 'ئەم ١٢ وشەم لە شوێنێکی سەلامەتدا نووسیوە',
conditionNeverShare: 'تێدەگەم کە هەرگیز نابێت ئەم وشانە لەگەڵ کەسێکدا هاوبەش بکەم',
conditionLossRisk: 'تێدەگەم کە ئەگەر ئەم وشانە لەدەست بدەم ناتوانم دەستم بە جزدانەکەم بگات',
acceptAllConditions: 'هەموو مەرجەکان پەسەندبکە',
verifyWords: 'پشتڕاستکردنەوەی وشەکان',
reset: 'ڕیسێتکردن',
verifyDescription: 'تکایە وشەکان بە ڕیزبەندی ڕاست ڕیزبکەرەوە',
dropWordsHere: 'وشەکان لێرە دابنێ...',
wrongOrder: 'ڕیزبەندی وشەکان هەڵەیە. تکایە دووبارە هەوڵبدەرەوە',
saving: 'پاشەکەوتکردن...',
verify: 'پشتڕاستکردنەوە',
wordsCount: '{count}/١٢ وشە',
walletCreated: 'جزدان دروستکرا!',
walletReady: 'جزدانەکەت ئامادەیە',
yourAddress: 'ناونیشانەکەت',
getStarted: 'دەستپێبکە',
},
walletImport: {
title: 'هاوردەکردنی جزدان',
description: 'وشەی نهێنی جزدانی ئامادەکەت بنووسە',
seedPhraseLabel: 'وشەی نهێنی (١٢ یان ٢٤ وشە)',
seedPhrasePlaceholder: 'وشەکان بە بۆشایی جیاکەرەوە بنووسە...',
wordsCount: '{count} / ١٢ وشە',
newPassword: 'وشەی نهێنی نوێ',
seedPhraseInvalid: 'وشەی نهێنی دەبێت ١٢ یان ٢٤ وشە بێت',
passwordInvalid: 'وشەی نهێنی نادروستە',
passwordsMismatch: 'وشەی نهێنییەکان وەکیەک نین',
importFailed: 'هاوردەکردن سەرکەوتوو نەبوو',
importing: 'هاوردەکردن...',
importButton: 'هاوردەکردن',
},
walletConnect: {
deleteTitle: 'جزدان بسڕیتەوە؟',
deleteDescription:
'ئەم کردارە ناگەڕێتەوە. ئەگەر وشەی نهێنیت نییە، ناتوانیت دەستت بە جزدانەکەت بگات.',
deleteButton: 'سڕینەوە',
openWallet: 'کردنەوەی جزدان',
passwordLabel: 'وشەی نهێنی',
passwordPlaceholder: 'وشەی نهێنی بنووسە',
enterPassword: 'وشەی نهێنی بنووسە',
wrongPassword: 'وشەی نهێنی هەڵەیە',
connecting: 'کردنەوە...',
connect: 'پەیوەستکردن',
deleteWalletLink: 'سڕینەوەی جزدان',
},
deposit: {
title: 'پارەدانانی USDT',
subtitle: 'بۆ wUSDT لەسەر Asset Hub',
depositHistory: 'مێژووی پارەدانان',
noDeposits: 'هێشتا هیچ پارەدانانێک نییە',
goBack: 'گەڕانەوە',
selectNetwork: 'تۆڕ هەڵبژێرە',
recommended: 'پێشنیارکراو',
warning: 'ئاگاداری!',
example: 'بۆ نموونە: ١٠ USDT بنێرە ← ٧ wUSDT وەردەگریت ($٣ کرێ)',
acceptTrc20: 'ڕازیم، لە ڕێگەی TRC20 بنێرە',
important: 'گرنگ!',
minimum: 'کەمترین: {amount} USDT',
memoRequired: 'کۆدەکەت لە خانەی Memo/Comment بنووسە',
onlyUsdt: 'تەنها USDT بنێرە، تۆکنی تر لەدەستدەچن',
trc20Fee: '$٣ کرێ لە بڕەکەت دەبڕدرێتەوە',
depositAddress: 'ناونیشانی پارەدانان',
notAvailable: 'بەردەست نییە',
memoLabel: 'Memo / Comment (پێویستە)',
memoWarning: 'ئەم کۆدە لە memo بنووسە، ئەگەرنا پارەدانانەکەت ناسراونەتەوە!',
uniqueAddress: 'ئەم ناونیشانە تایبەتە بە تۆ. پێویستی بە memo نییە.',
howToDeposit: 'چۆن پارە دادەنێیت؟',
stepCopyAddress: 'ناونیشانەکە لەبەربگرەوە',
stepCopyMemo: 'کۆدی memo لەبەربگرەوە',
stepOpenTon: 'جزدانی تێلێگرام یان جزدانەکەت بکەرەوە',
stepOpenPolkadot: 'جزدانی Polkadot ـەکەت بکەرەوە',
stepOpenTrc20: 'TronLink یان جزدانەکەت بکەرەوە',
stepSendUsdt: 'USDT بنێرە بۆ ناونیشانی سەرەوە',
stepReceive: 'wUSDT لە چەند خولەکێکدا دەگاتە ئەکاونتەکەت',
processingTime: 'کاتی جێبەجێکردن: ~١-٥ خولەک',
copyFailed: 'لەبەرگرتنەوە سەرکەوتوو نەبوو',
statusPending: 'چاوەڕوان',
statusConfirming: 'پشتڕاستکردنەوە',
statusCompleted: 'تەواوبوو',
statusFailed: 'سەرکەوتوو نەبوو',
statusExpired: 'بەسەرچوو',
viewTx: 'بینینی TX',
trc20FeeWarning: 'کرێی تۆڕی TRC20 نزیکەی $٣ ـە. ئێمە تۆڕی TON یان Polkadot پێشنیار دەکەین.',
},
p2p: {
title: 'ئاڵوگۆڕی P2P',
subtitle: 'ئاڵوگۆڕی کریپتۆ نێوان کەسەکان',
firstTime: 'یەکەم جارە P2P بەکاردەهێنیت؟',
steps: [
'کلیک لەسەر دوگمەی خوارەوە بکە بۆ کردنەوەی وێب ئەپ',
'ئەکاونتێک دروستبکە یان بچۆ ژوورەوە',
'ڕێکخستنی P2P تەواو بکە',
'دوای ڕێکخستن، دەتوانیت ڕاستەوخۆ دەستت بە P2P بگات',
],
note: 'وێب ئەپ لە پەنجەرەیەکی نوێدا دەکرێتەوە. ڕێکخستنەکە لەوێ تەواو بکە.',
button: 'کردنەوەی پلاتفۆرمی P2P',
},
update: {
newVersion: 'وەشانی نوێ بەردەستە!',
description: 'نوێکردنەوە بۆ تایبەتمەندی نوێ و چاکسازی ئاسایشی.',
later: 'دواتر',
updateNow: 'نوێکردنەوە',
},
social: {
followUs: 'شوێنمان بکەوە',
stayConnected: 'پەیوەندیدار بمێنە و تازەترین هەواڵەکان وەربگرە!',
instagram: 'وێنە و ستۆری',
tiktok: 'ڤیدیۆی کورت',
snapchat: 'سناپمان بۆ بنێرە!',
telegram: 'کەناڵی فەرمی',
twitter: 'هەواڵی ڕۆژانە',
youtube: 'ڤیدیۆکانمان',
facebook: 'پەیجی فەرمی',
discord: 'کۆمەڵگاکەمان',
},
errorBoundary: {
title: 'کێشەیەک ڕوویدا',
description: 'ببوورە، هەڵەیەکی تەکنیکی ڕوویدا. تکایە دووبارە هەوڵبدەرەوە.',
retry: 'دووبارە هەوڵبدەرەوە',
},
loadingScreen: {
loading: 'بارکردن...',
},
};
export default ckb;
+305
View File
@@ -0,0 +1,305 @@
import type { Translations } from '../types';
const en: Translations = {
nav: {
announcements: 'News',
forum: 'Forum',
rewards: 'Rewards',
p2p: 'P2P',
wallet: 'Wallet',
},
common: {
back: 'Back',
cancel: 'Cancel',
continue: 'Continue',
close: 'Close',
copy: 'Copy',
copied: 'Copied!',
share: 'Share',
refresh: 'Refresh',
retry: 'Try again',
loading: 'Loading...',
error: 'Error',
anonymous: 'Anonymous',
pinned: 'Pinned',
locked: 'Locked',
trending: 'Trending',
},
announcements: {
title: 'Announcements',
readMore: 'Read more',
reactionAuthRequired: 'You must be in Telegram to vote',
},
forum: {
title: 'Forum',
newTopic: 'New Topic',
submitting: 'Submitting...',
publish: 'Publish',
category: 'Category',
topicTitle: 'Title',
topicTitlePlaceholder: 'Topic title...',
content: 'Content',
contentPlaceholder: 'Write the topic content...',
tagsLabel: 'Tags (separate with comma)',
tagsPlaceholder: 'blockchain, kurd, pez...',
searchPlaceholder: 'Search topics...',
sortRecent: 'New',
sortPopular: 'Popular',
sortReplies: 'Replies',
sortViews: 'Views',
all: 'All',
replies: 'Replies',
noRepliesYet: 'No replies yet',
beFirstToReply: 'Be the first to reply!',
replyPlaceholder: 'Write your reply...',
noTopicsFound: 'No topics found',
changeFilters: 'Change your filters',
createNewTopic: 'Create New Topic',
loginToVote: 'Log in to vote',
voteError: 'Error voting',
writeReply: 'Please write your reply',
topicLocked: 'This topic is locked',
replyError: 'Error sending reply',
fillAllFields: 'Please fill in all fields',
topicCreated: 'Topic created!',
topicCreateError: 'Error creating topic',
},
rewards: {
title: 'Rewards',
subtitle: 'Rewards - Referral System',
connectWalletFirst: 'To see your referrals and rewards, connect your wallet first.',
overview: 'Overview',
referral: 'Referral',
scores: 'Scores',
referralScore: 'Referral Score',
maxScore: 'Max score: 500',
referMoreTitle: 'Refer more, earn more!',
referMoreDescription:
'For every person you bring, you receive HEZ and PEZ as rewards. More referrals = more rewards!',
kycApproved: 'KYC Approved',
referrer: 'Referrer',
none: 'None',
invitedMe: 'Invited me',
pendingReferral: 'Pending referral',
completeKyc: 'Complete KYC to approve referral from',
inviteFriends: 'Invite your friends',
yourLink: 'Your link',
copySuccess: 'Copied!',
copyLink: 'Copy',
shareLink: 'Share',
scoreSystem: 'Score system',
activeStatus: 'Activity Status',
timeRemaining: 'Time: {time} remaining',
active: 'Active',
inactive: 'Inactive',
activeDescription: 'Click once every 24 hours to stay active and earn more rewards!',
youAreActive: 'You are Active!',
iAmActive: 'I am Active!',
activatedAlert: 'You are now active! Click again after 24 hours.',
shareText: 'Pezkuwichain - Digital State of Kurdistan! Join us through my link:',
copyAlert: 'Copied',
referralCount: '{count} referrals (KYC approved)',
noReferrals: 'You have no referrals yet',
shareYourLink: 'Share your link!',
trustScore: 'Trust Score',
rank: 'Rank: {rating}',
citizen: 'Citizen',
staking: 'Staking',
stakingNotStarted: 'Not started',
stakingCountedInTrust: 'Counted in Trust score',
people: '{count} people',
tiki: 'Tiki',
nftRole: 'NFT Role',
education: 'Education',
reading: 'Reading',
stakingRewards: 'Staking Rewards',
totalRewards: 'Total rewards received',
recentRewards: 'Recent rewards',
noRewardsYet: 'No rewards recorded yet',
scoreFormula: 'Score Formula',
stakingZeroWarning: 'If Staking is 0, Trust score is also 0. Stake first!',
refreshScores: 'Refresh Scores',
points: 'points',
},
wallet: {
title: 'Wallet',
authFailed: 'Authentication Failed',
authDescription: 'Please make sure you open this app within Telegram',
retry: 'Try Again',
},
walletSetup: {
officialWallet: 'Official Pezkuwichain wallet',
createNew: 'Create New Wallet',
createNewDesc: 'Create a new wallet with seed phrase',
importWallet: 'Import Wallet',
importWalletDesc: 'Use your existing seed phrase',
securityNote:
'Your wallet is securely stored on your device. We never have access to your keys.',
},
walletCreate: {
setPassword: 'Set Password',
passwordDescription: 'This password will be used to unlock your wallet',
passwordLabel: 'Password',
passwordPlaceholder: 'At least 12 characters',
confirmPasswordLabel: 'Confirm Password',
confirmPasswordPlaceholder: 'Confirm your password',
passwordRequirements: 'Password Requirements:',
ruleMinLength: 'At least 12 characters',
ruleLowercase: 'At least 1 lowercase letter (a-z)',
ruleUppercase: 'At least 1 uppercase letter (A-Z)',
ruleNumber: 'At least 1 number (0-9)',
ruleSpecialChar: 'At least 1 special character (!@#$%...)',
rulePasswordsMatch: 'Passwords match',
walletServiceNotReady: 'Wallet service is not ready. Please wait.',
passwordRequirementsNotMet: 'Please meet all password requirements',
walletCreationFailed: 'Wallet creation failed. Please try again',
preparing: 'Preparing...',
creating: 'Creating...',
meetPasswordRequirements: 'Meet password requirements',
backupTitle: 'Backup Seed Phrase',
backupDescription: 'These 12 words are your wallet. Write them in a safe place!',
backupWarning:
'Important: These words are shown only once. If you lose them, you cannot access your wallet.',
copiedMnemonic: 'Copied!',
copyMnemonic: 'Copy',
conditionWrittenDown: 'I have written down these 12 words in a safe place',
conditionNeverShare: 'I understand I must never share these words with anyone',
conditionLossRisk: 'I understand if I lose these words I cannot access my wallet',
acceptAllConditions: 'Accept all conditions',
verifyWords: 'Verify Words',
reset: 'Reset',
verifyDescription: 'Please arrange the words in the correct order',
dropWordsHere: 'Place words here...',
wrongOrder: 'Wrong word order. Please try again',
saving: 'Saving...',
verify: 'Verify',
wordsCount: '{count}/12 words',
walletCreated: 'Wallet Created!',
walletReady: 'Your wallet is ready',
yourAddress: 'Your address',
getStarted: 'Get Started',
},
walletImport: {
title: 'Import Wallet',
description: 'Enter your existing wallet seed phrase',
seedPhraseLabel: 'Seed Phrase (12 or 24 words)',
seedPhrasePlaceholder: 'Enter your words separated by spaces...',
wordsCount: '{count} / 12 words',
newPassword: 'New Password',
seedPhraseInvalid: 'Seed phrase must be 12 or 24 words',
passwordInvalid: 'Password is invalid',
passwordsMismatch: 'Passwords do not match',
importFailed: 'Import failed',
importing: 'Importing...',
importButton: 'Import',
},
walletConnect: {
deleteTitle: 'Delete Wallet?',
deleteDescription:
"This action cannot be undone. If you don't have your seed phrase, you will not be able to access your wallet.",
deleteButton: 'Delete',
openWallet: 'Open Wallet',
passwordLabel: 'Password',
passwordPlaceholder: 'Enter your password',
enterPassword: 'Enter your password',
wrongPassword: 'Wrong password',
connecting: 'Opening...',
connect: 'Connect',
deleteWalletLink: 'Delete wallet',
},
deposit: {
title: 'Deposit USDT',
subtitle: 'For wUSDT on Asset Hub',
depositHistory: 'Deposit History',
noDeposits: 'No deposits yet',
goBack: 'Go back',
selectNetwork: 'Select Network',
recommended: 'Recommended',
warning: 'Warning!',
example: 'Example: Send 10 USDT \u2192 Receive 7 wUSDT ($3 fee)',
acceptTrc20: 'I accept, send via TRC20',
important: 'Important!',
minimum: 'Minimum: {amount} USDT',
memoRequired: 'Write your code in the Memo/Comment field',
onlyUsdt: 'Only send USDT, other tokens will be lost',
trc20Fee: '$3 fee will be deducted from your amount',
depositAddress: 'Deposit Address',
notAvailable: 'Not available',
memoLabel: 'Memo / Comment (REQUIRED)',
memoWarning: 'Write this code in the memo, otherwise your deposit will not be recognized!',
uniqueAddress: 'This address is uniquely yours. No memo needed.',
howToDeposit: 'How to Deposit?',
stepCopyAddress: 'Copy the address',
stepCopyMemo: 'Copy the memo code',
stepOpenTon: 'Open Telegram Wallet or your wallet',
stepOpenPolkadot: 'Open your Polkadot wallet',
stepOpenTrc20: 'Open TronLink or your wallet',
stepSendUsdt: 'Send USDT to the address above',
stepReceive: 'wUSDT will be in your account within a few minutes',
processingTime: 'Processing time: ~1-5 minutes',
copyFailed: 'Copy failed',
statusPending: 'Pending',
statusConfirming: 'Confirming',
statusCompleted: 'Completed',
statusFailed: 'Failed',
statusExpired: 'Expired',
viewTx: 'View TX',
trc20FeeWarning: 'TRC20 network fee is about $3. We recommend TON or Polkadot network.',
},
p2p: {
title: 'P2P Exchange',
subtitle: 'Trade crypto peer-to-peer',
firstTime: 'First time using P2P?',
steps: [
'Click the button below to open the web app',
'Create an account or log in',
'Complete the P2P setup process',
'After setup, you can access P2P directly',
],
note: 'The web app will open in a new window. Complete the registration process there.',
button: 'Open P2P Platform',
},
update: {
newVersion: 'New version available!',
description: 'Update for new features and security fixes.',
later: 'Later',
updateNow: 'Update',
},
social: {
followUs: 'Follow us',
stayConnected: 'Stay connected and get the latest news!',
instagram: 'Photos & Stories',
tiktok: 'Short videos',
snapchat: 'Snap us!',
telegram: 'Official channel',
twitter: 'Daily news',
youtube: 'Our videos',
facebook: 'Official page',
discord: 'Our community',
},
errorBoundary: {
title: 'Something went wrong',
description: 'Sorry, a technical error occurred. Please try again.',
retry: 'Try again',
},
loadingScreen: {
loading: 'Loading...',
},
};
export default en;
+305
View File
@@ -0,0 +1,305 @@
import type { Translations } from '../types';
const fa: Translations = {
nav: {
announcements: 'اخبار',
forum: 'فروم',
rewards: 'جوایز',
p2p: 'P2P',
wallet: 'کیف پول',
},
common: {
back: 'بازگشت',
cancel: 'لغو',
continue: 'ادامه',
close: 'بستن',
copy: 'کپی',
copied: 'کپی شد!',
share: 'اشتراک‌گذاری',
refresh: 'بازنشانی',
retry: 'دوباره تلاش کنید',
loading: 'در حال بارگذاری...',
error: 'خطا',
anonymous: 'ناشناس',
pinned: 'سنجاق شده',
locked: 'قفل شده',
trending: 'داغ',
},
announcements: {
title: 'اطلاعیه‌ها',
readMore: 'بیشتر بخوانید',
reactionAuthRequired: 'برای رأی دادن باید در تلگرام باشید',
},
forum: {
title: 'فروم',
newTopic: 'موضوع جدید',
submitting: 'در حال ارسال...',
publish: 'انتشار',
category: 'دسته‌بندی',
topicTitle: 'عنوان',
topicTitlePlaceholder: 'عنوان موضوع...',
content: 'محتوا',
contentPlaceholder: 'محتوای موضوع را بنویسید...',
tagsLabel: 'برچسب‌ها (با کاما جدا کنید)',
tagsPlaceholder: 'blockchain, kurd, pez...',
searchPlaceholder: 'جستجوی موضوعات...',
sortRecent: 'جدید',
sortPopular: 'محبوب',
sortReplies: 'پاسخ‌ها',
sortViews: 'بازدیدها',
all: 'همه',
replies: 'پاسخ‌ها',
noRepliesYet: 'هنوز پاسخی نیست',
beFirstToReply: 'اولین نفری باشید که پاسخ می‌دهد!',
replyPlaceholder: 'پاسخ خود را بنویسید...',
noTopicsFound: 'موضوعی یافت نشد',
changeFilters: 'فیلترهای خود را تغییر دهید',
createNewTopic: 'ایجاد موضوع جدید',
loginToVote: 'برای رأی دادن وارد شوید',
voteError: 'خطا در رأی دادن',
writeReply: 'لطفاً پاسخ خود را بنویسید',
topicLocked: 'این موضوع قفل شده است',
replyError: 'خطا در ارسال پاسخ',
fillAllFields: 'لطفاً تمام فیلدها را پر کنید',
topicCreated: 'موضوع ایجاد شد!',
topicCreateError: 'خطا در ایجاد موضوع',
},
rewards: {
title: 'جوایز',
subtitle: 'جوایز - سیستم دعوت',
connectWalletFirst: 'برای مشاهده دعوت‌ها و جوایز، ابتدا کیف پول خود را متصل کنید.',
overview: 'نمای کلی',
referral: 'دعوت',
scores: 'امتیازها',
referralScore: 'امتیاز دعوت',
maxScore: 'حداکثر امتیاز: ۵۰۰',
referMoreTitle: 'بیشتر دعوت کنید، بیشتر کسب کنید!',
referMoreDescription:
'به ازای هر نفری که دعوت کنید، HEZ و PEZ به عنوان جایزه دریافت می‌کنید. دعوت بیشتر = جایزه بیشتر!',
kycApproved: 'KYC تأیید شده',
referrer: 'دعوت‌کننده',
none: 'هیچ',
invitedMe: 'مرا دعوت کرده',
pendingReferral: 'دعوت در انتظار',
completeKyc: 'KYC را تکمیل کنید تا دعوت تأیید شود از',
inviteFriends: 'دوستان خود را دعوت کنید',
yourLink: 'لینک شما',
copySuccess: 'کپی شد!',
copyLink: 'کپی',
shareLink: 'اشتراک‌گذاری',
scoreSystem: 'سیستم امتیازدهی',
activeStatus: 'وضعیت فعالیت',
timeRemaining: 'زمان: {time} باقی‌مانده',
active: 'فعال',
inactive: 'غیرفعال',
activeDescription: 'هر ۲۴ ساعت یک بار کلیک کنید تا فعال بمانید و جوایز بیشتری کسب کنید!',
youAreActive: 'شما فعال هستید!',
iAmActive: 'من فعال هستم!',
activatedAlert: 'شما اکنون فعال هستید! پس از ۲۴ ساعت دوباره کلیک کنید.',
shareText: 'Pezkuwichain - دولت دیجیتال کردستان! از طریق لینک من بپیوندید:',
copyAlert: 'کپی شد',
referralCount: '{count} دعوت (KYC تأیید شده)',
noReferrals: 'شما هنوز دعوتی ندارید',
shareYourLink: 'لینک خود را به اشتراک بگذارید!',
trustScore: 'امتیاز اعتماد',
rank: 'رتبه: {rating}',
citizen: 'شهروند',
staking: 'سهام‌گذاری',
stakingNotStarted: 'شروع نشده',
stakingCountedInTrust: 'در امتیاز اعتماد محاسبه می‌شود',
people: '{count} نفر',
tiki: 'تیکی',
nftRole: 'نقش NFT',
education: 'آموزش',
reading: 'مطالعه',
stakingRewards: 'جوایز سهام‌گذاری',
totalRewards: 'کل جوایز دریافت شده',
recentRewards: 'جوایز اخیر',
noRewardsYet: 'هنوز جایزه‌ای ثبت نشده',
scoreFormula: 'فرمول امتیاز',
stakingZeroWarning: 'اگر سهام‌گذاری ۰ باشد، امتیاز اعتماد نیز ۰ است. ابتدا سهام‌گذاری کنید!',
refreshScores: 'بازنشانی امتیازها',
points: 'امتیاز',
},
wallet: {
title: 'کیف پول',
authFailed: 'احراز هویت ناموفق',
authDescription: 'لطفاً مطمئن شوید که این برنامه را در تلگرام باز کرده‌اید',
retry: 'دوباره تلاش کنید',
},
walletSetup: {
officialWallet: 'کیف پول رسمی Pezkuwichain',
createNew: 'ایجاد کیف پول جدید',
createNewDesc: 'ایجاد کیف پول جدید با عبارت بازیابی',
importWallet: 'وارد کردن کیف پول',
importWalletDesc: 'استفاده از عبارت بازیابی موجود',
securityNote:
'کیف پول شما به صورت امن در دستگاه شما ذخیره می‌شود. ما هرگز به کلیدهای شما دسترسی نداریم.',
},
walletCreate: {
setPassword: 'تنظیم رمز عبور',
passwordDescription: 'این رمز عبور برای باز کردن قفل کیف پول شما استفاده می‌شود',
passwordLabel: 'رمز عبور',
passwordPlaceholder: 'حداقل ۱۲ کاراکتر',
confirmPasswordLabel: 'تأیید رمز عبور',
confirmPasswordPlaceholder: 'رمز عبور خود را تأیید کنید',
passwordRequirements: 'الزامات رمز عبور:',
ruleMinLength: 'حداقل ۱۲ کاراکتر',
ruleLowercase: 'حداقل ۱ حرف کوچک (a-z)',
ruleUppercase: 'حداقل ۱ حرف بزرگ (A-Z)',
ruleNumber: 'حداقل ۱ عدد (0-9)',
ruleSpecialChar: 'حداقل ۱ کاراکتر ویژه (!@#$%...)',
rulePasswordsMatch: 'رمزهای عبور مطابقت دارند',
walletServiceNotReady: 'سرویس کیف پول آماده نیست. لطفاً صبر کنید.',
passwordRequirementsNotMet: 'لطفاً تمام الزامات رمز عبور را رعایت کنید',
walletCreationFailed: 'ایجاد کیف پول ناموفق بود. لطفاً دوباره تلاش کنید',
preparing: 'در حال آماده‌سازی...',
creating: 'در حال ایجاد...',
meetPasswordRequirements: 'الزامات رمز عبور را رعایت کنید',
backupTitle: 'پشتیبان‌گیری از عبارت بازیابی',
backupDescription: 'این ۱۲ کلمه کیف پول شما هستند. آن‌ها را در جای امنی بنویسید!',
backupWarning:
'مهم: این کلمات فقط یک بار نمایش داده می‌شوند. اگر آن‌ها را گم کنید، نمی‌توانید به کیف پول خود دسترسی پیدا کنید.',
copiedMnemonic: 'کپی شد!',
copyMnemonic: 'کپی',
conditionWrittenDown: 'این ۱۲ کلمه را در جای امنی نوشته‌ام',
conditionNeverShare: 'می‌دانم که هرگز نباید این کلمات را با کسی به اشتراک بگذارم',
conditionLossRisk: 'می‌دانم که اگر این کلمات را گم کنم نمی‌توانم به کیف پولم دسترسی پیدا کنم',
acceptAllConditions: 'پذیرش تمام شرایط',
verifyWords: 'تأیید کلمات',
reset: 'بازنشانی',
verifyDescription: 'لطفاً کلمات را به ترتیب صحیح مرتب کنید',
dropWordsHere: 'کلمات را اینجا قرار دهید...',
wrongOrder: 'ترتیب کلمات اشتباه است. لطفاً دوباره تلاش کنید',
saving: 'در حال ذخیره...',
verify: 'تأیید',
wordsCount: '{count}/۱۲ کلمه',
walletCreated: 'کیف پول ایجاد شد!',
walletReady: 'کیف پول شما آماده است',
yourAddress: 'آدرس شما',
getStarted: 'شروع کنید',
},
walletImport: {
title: 'وارد کردن کیف پول',
description: 'عبارت بازیابی کیف پول موجود خود را وارد کنید',
seedPhraseLabel: 'عبارت بازیابی (۱۲ یا ۲۴ کلمه)',
seedPhrasePlaceholder: 'کلمات خود را با فاصله وارد کنید...',
wordsCount: '{count} / ۱۲ کلمه',
newPassword: 'رمز عبور جدید',
seedPhraseInvalid: 'عبارت بازیابی باید ۱۲ یا ۲۴ کلمه باشد',
passwordInvalid: 'رمز عبور نامعتبر است',
passwordsMismatch: 'رمزهای عبور مطابقت ندارند',
importFailed: 'وارد کردن ناموفق بود',
importing: 'در حال وارد کردن...',
importButton: 'وارد کردن',
},
walletConnect: {
deleteTitle: 'حذف کیف پول؟',
deleteDescription:
'این عمل قابل بازگشت نیست. اگر عبارت بازیابی خود را ندارید، نمی‌توانید به کیف پول خود دسترسی پیدا کنید.',
deleteButton: 'حذف',
openWallet: 'باز کردن کیف پول',
passwordLabel: 'رمز عبور',
passwordPlaceholder: 'رمز عبور خود را وارد کنید',
enterPassword: 'رمز عبور خود را وارد کنید',
wrongPassword: 'رمز عبور اشتباه است',
connecting: 'در حال باز کردن...',
connect: 'اتصال',
deleteWalletLink: 'حذف کیف پول',
},
deposit: {
title: 'واریز USDT',
subtitle: 'برای wUSDT در Asset Hub',
depositHistory: 'تاریخچه واریز',
noDeposits: 'هنوز واریزی انجام نشده',
goBack: 'بازگشت',
selectNetwork: 'انتخاب شبکه',
recommended: 'پیشنهادی',
warning: 'هشدار!',
example: 'مثال: ارسال ۱۰ USDT \u2192 دریافت ۷ wUSDT (کارمزد ۳ دلار)',
acceptTrc20: 'قبول می‌کنم، از طریق TRC20 ارسال کنید',
important: 'مهم!',
minimum: 'حداقل: {amount} USDT',
memoRequired: 'کد خود را در فیلد یادداشت/توضیحات بنویسید',
onlyUsdt: 'فقط USDT ارسال کنید، توکن‌های دیگر از بین می‌روند',
trc20Fee: 'کارمزد ۳ دلار از مبلغ شما کسر می‌شود',
depositAddress: 'آدرس واریز',
notAvailable: 'در دسترس نیست',
memoLabel: 'یادداشت / توضیحات (الزامی)',
memoWarning: 'این کد را در یادداشت بنویسید، در غیر این صورت واریز شما شناسایی نمی‌شود!',
uniqueAddress: 'این آدرس مختص شماست. نیازی به یادداشت نیست.',
howToDeposit: 'چگونه واریز کنم؟',
stepCopyAddress: 'آدرس را کپی کنید',
stepCopyMemo: 'کد یادداشت را کپی کنید',
stepOpenTon: 'کیف پول تلگرام یا کیف پول خود را باز کنید',
stepOpenPolkadot: 'کیف پول Polkadot خود را باز کنید',
stepOpenTrc20: 'TronLink یا کیف پول خود را باز کنید',
stepSendUsdt: 'USDT را به آدرس بالا ارسال کنید',
stepReceive: 'wUSDT ظرف چند دقیقه در حساب شما خواهد بود',
processingTime: 'زمان پردازش: حدود ۱ تا ۵ دقیقه',
copyFailed: 'کپی ناموفق بود',
statusPending: 'در انتظار',
statusConfirming: 'در حال تأیید',
statusCompleted: 'تکمیل شده',
statusFailed: 'ناموفق',
statusExpired: 'منقضی شده',
viewTx: 'مشاهده تراکنش',
trc20FeeWarning: 'کارمزد شبکه TRC20 حدود ۳ دلار است. شبکه TON یا Polkadot پیشنهاد می‌شود.',
},
p2p: {
title: 'صرافی P2P',
subtitle: 'معامله رمزارز به صورت همتا به همتا',
firstTime: 'اولین بار است که از P2P استفاده می‌کنید؟',
steps: [
'روی دکمه زیر کلیک کنید تا برنامه وب باز شود',
'یک حساب بسازید یا وارد شوید',
'فرآیند راه‌اندازی P2P را تکمیل کنید',
'پس از راه‌اندازی، می‌توانید مستقیماً به P2P دسترسی پیدا کنید',
],
note: 'برنامه وب در پنجره جدیدی باز می‌شود. فرآیند ثبت‌نام را در آنجا تکمیل کنید.',
button: 'باز کردن پلتفرم P2P',
},
update: {
newVersion: 'نسخه جدید موجود است!',
description: 'برای ویژگی‌های جدید و رفع مشکلات امنیتی به‌روزرسانی کنید.',
later: 'بعداً',
updateNow: 'به‌روزرسانی',
},
social: {
followUs: 'ما را دنبال کنید',
stayConnected: 'در ارتباط باشید و آخرین اخبار را دریافت کنید!',
instagram: 'عکس‌ها و استوری‌ها',
tiktok: 'ویدیوهای کوتاه',
snapchat: 'اسنپ بفرستید!',
telegram: 'کانال رسمی',
twitter: 'اخبار روزانه',
youtube: 'ویدیوهای ما',
facebook: 'صفحه رسمی',
discord: 'جامعه ما',
},
errorBoundary: {
title: 'مشکلی پیش آمد',
description: 'متأسفیم، یک خطای فنی رخ داد. لطفاً دوباره تلاش کنید.',
retry: 'دوباره تلاش کنید',
},
loadingScreen: {
loading: 'در حال بارگذاری...',
},
};
export default fa;
+320
View File
@@ -0,0 +1,320 @@
import type { Translations } from '../types';
const krd: Translations = {
nav: {
announcements: 'Ragihandin',
forum: 'Forum',
rewards: 'Xelat',
p2p: 'P2P',
wallet: 'Ber\u00eek',
},
common: {
back: 'Pa\u015f',
cancel: 'Betal',
continue: 'Berdewam',
close: 'Bigire',
copy: 'Kop\u00ee bike',
copied: 'Kop\u00ee b\u00fb!',
share: 'Parve bike',
refresh: 'N\u00fbve bike',
retry: 'D\u00eesa bicerb\u00eene',
loading: 'T\u00ea barkirin...',
error: '\u00c7ewt\u00ee',
anonymous: 'Anonymous',
pinned: 'Pinned',
locked: 'Kil\u00eetk\u00eer\u00ee',
trending: 'Trending',
},
announcements: {
title: 'Ragihandin',
readMore: 'Z\u00eadetir bixw\u00eene',
reactionAuthRequired: 'Ji bo dengdan\u00ea div\u00ea tu di Telegram\u00ea de b\u00ee',
},
forum: {
title: 'Forum',
newTopic: 'Mijara N\u00fb',
submitting: 'T\u00ea \u015fandin...',
publish: 'Biwe\u015f\u00eene',
category: 'Kategor\u00ee',
topicTitle: 'Sernav',
topicTitlePlaceholder: 'Nav\u00ea mijar\u00ea...',
content: 'Naverok',
contentPlaceholder: 'Naveroka mijar\u00ea bin\u00eev\u00eese...',
tagsLabel: 'Et\u00eeket (bi virgul\u00ea cuda bike)',
tagsPlaceholder: 'blockchain, kurd, pez...',
searchPlaceholder: 'Mijar bigere...',
sortRecent: 'N\u00fb',
sortPopular: 'Populer',
sortReplies: 'Bersiv',
sortViews: 'D\u00eetin',
all: 'Hem\u00fb',
replies: 'Bersiv',
noRepliesYet: 'H\u00eaj bersiv tune ye',
beFirstToReply: 'Yekem\u00een bersiv\u00ea tu bide!',
replyPlaceholder: 'Bersiva xwe bin\u00eev\u00eese...',
noTopicsFound: 'Mijar nehat d\u00eetin',
changeFilters: 'Filter\u00ean xwe biguh\u00eare',
createNewTopic: 'Mijara N\u00fb Biaf\u00eer\u00eene',
loginToVote: 'Ji bo dengdan\u00ea t\u00eakeve',
voteError: '\u00c7ewt\u00ee di dengdan\u00ea de',
writeReply: 'Ji kerema xwe bersiva xwe bin\u00eev\u00eese',
topicLocked: 'Ev mijar kil\u00eetk\u00eer\u00ee ye',
replyError: '\u00c7ewt\u00ee di \u015fandina bersiv\u00ea de',
fillAllFields: 'Ji kerema xwe hem\u00fb qadan tije bike',
topicCreated: 'Mijar hat afirandin!',
topicCreateError: '\u00c7ewt\u00ee di afirandina mijar\u00ea de',
},
rewards: {
title: 'Xelat',
subtitle: 'Xelat - Referral System',
connectWalletFirst:
'Ji bo d\u00eetina referral \u00fb xelat\u00ean xwe, ber\u00ee her ti\u015ft\u00ee c\u00eezdan\u00ea xwe gir\u00eade.',
overview: 'Ge\u015fb\u00een',
referral: 'Referral',
scores: 'Xal',
referralScore: 'P\u00fbana Referral',
maxScore: 'Max p\u00fban: 500',
referMoreTitle:
'\u0632\u06cc\u0627\u062a\u0631 \u0695\u06cc\u0641\u06d5\u0631 \u0628\u06a9\u06d5\u060c \u0632\u06cc\u0627\u062a\u0631 \u0642\u0627\u0632\u0627\u0646\u062c \u0628\u06a9\u06d5!',
referMoreDescription:
'\u0647\u06d5\u0631 \u06a9\u06d5\u0633\u06ce\u06a9 \u0628\u0647\u06ce\u0646\u06cc\u062a\u060c HEZ \u0648 PEZ \u0648\u06d5\u06a9 \u062e\u06d5\u06b5\u0627\u062a \u0648\u06d5\u0631\u062f\u06d5\u06af\u0631\u06cc\u062a. \u0632\u06cc\u0627\u062a\u0631 \u0695\u06cc\u0641\u06d5\u0631 = \u0632\u06cc\u0627\u062a\u0631 \u062e\u06d5\u06b5\u0627\u062a!',
kycApproved: 'KYC pejirand\u00ee',
referrer: 'Referrer',
none: 'Tune',
invitedMe: 'Min vexwand',
pendingReferral: 'Referral li bend\u00ea',
completeKyc: 'KYC temam bike ji bo pejirandina referral ji',
inviteFriends: 'Heval\u00ean xwe vexw\u00eene',
yourLink: 'L\u00eenka te',
copySuccess: 'Kop\u00ee b\u00fb!',
copyLink: 'Kop\u00ee bike',
shareLink: 'Parve bike',
scoreSystem: 'S\u00eestema p\u00fbanan',
activeStatus: 'Rew\u015fa Akt\u00eevb\u00fbn\u00ea',
timeRemaining: 'Dem: {time} maye',
active: 'Akt\u00eev',
inactive: 'Ne Akt\u00eev',
activeDescription:
'Her 24 saet carek\u00ea bikirt\u00eene da ku akt\u00eev bim\u00een\u00ee \u00fb xelat\u00ean z\u00eadetir qezenc bik\u00ee!',
youAreActive: 'Tu Akt\u00eev \u00ee!',
iAmActive: 'Ez Akt\u00eev im!',
activatedAlert: 'Tu niha akt\u00eev \u00ee! 24 saet pa\u015f\u00ea d\u00eesa bikirt\u00eene.',
shareText:
'Pezkuwichain - Dewleta D\u00eej\u00eetal a Kurd! Bi l\u00eenka min ve tev li me bibe:',
copyAlert: 'Kop\u00ee b\u00fb',
referralCount: '{count} referral (KYC pejirand\u00ee)',
noReferrals: 'H\u00eaj referral\u00ean te tune ne',
shareYourLink: 'L\u00eenka xwe parve bike!',
trustScore: 'P\u00fbana P\u00eabaweriy\u00ea (Trust)',
rank: 'R\u00eaze: {rating}',
citizen: 'Welat\u00ee',
staking: 'Staking',
stakingNotStarted: 'Nehat\u00eeye destp\u00eakirin',
stakingCountedInTrust: 'Di Trust de t\u00ea hesibandin',
people: '{count} kes',
tiki: 'Tiki',
nftRole: 'Rola NFT',
education: 'Perwerde',
reading: 'Xwendin',
stakingRewards: 'Xelat\u00ean Staking',
totalRewards: 'Tevah\u00eeya xelat\u00ean wergirt\u00ee',
recentRewards: 'Xelat\u00ean daw\u00ee',
noRewardsYet: 'H\u00eaj xelatek nehat\u00eeye tomark\u00eerin',
scoreFormula: 'Form\u00fbla P\u00fban\u00ea',
stakingZeroWarning:
'Staking 0 be, Trust p\u00fban j\u00ee 0 dibe. Ber\u00ee her ti\u015ft\u00ee stake bike!',
refreshScores: 'P\u00fbanan N\u00fbve Bike',
points: 'p\u00fban',
},
wallet: {
title: 'Ber\u00eek',
authFailed: 'Teketin Tek Cu',
authDescription: 'Ji kerema xwe pistrast bikin ku hun ve app-e di nav Telegram de vedikin',
retry: 'Disa Biceribine',
},
walletSetup: {
officialWallet: 'Ber\u00eeka ferm\u00ee ya Pezkuwichain',
createNew: 'Wallet N\u00fb \u00c7\u00eabike',
createNewDesc: "Wallet'ek\u00ee n\u00fb bi seed phrase \u00e7\u00eabike",
importWallet: 'Wallet Import Bike',
importWalletDesc: "Seed phrase'\u00ea xwe y\u00ea hey\u00ee bi kar b\u00eene",
securityNote:
"Wallet'\u00ea te bi ewleh\u00ee li c\u00eehaza te t\u00ea hilan\u00een. Em tu car\u00ee gih\u00ee\u015ftina mift\u00eey\u00ean te tune.",
},
walletCreate: {
setPassword: '\u015e\u00eefre (Password) Diyar Bike',
passwordDescription:
"Ev \u015f\u00eefre (password) d\u00ea ji bo vekirina wallet'\u00ea were bikar\u00een\u00een",
passwordLabel: '\u015e\u00eefre (Password)',
passwordPlaceholder: 'Her\u00ee k\u00eam 12 t\u00eep (min 12 characters)',
confirmPasswordLabel: '\u015e\u00eefre Dubare (Confirm Password)',
confirmPasswordPlaceholder: '\u015e\u00eefre dubare bin\u00eev\u00eese (confirm password)',
passwordRequirements: '\u015eert\u00ean \u015e\u00eefre (Password):',
ruleMinLength: 'Her\u00ee k\u00eam 12 t\u00eep (min 12 characters)',
ruleLowercase: 'Her\u00ee k\u00eam 1 t\u00eepa bi\u00e7\u00fbk (a-z)',
ruleUppercase: 'Her\u00ee k\u00eam 1 t\u00eepa mezin (A-Z)',
ruleNumber: 'Her\u00ee k\u00eam 1 hejmar (0-9)',
ruleSpecialChar: 'Her\u00ee k\u00eam 1 sembola taybet\u00ee (!@#$%...)',
rulePasswordsMatch: '\u015e\u00eefre (password) hev digirin',
walletServiceNotReady: 'Wallet service amade n\u00eene. Ji kerema xwe bisekinin.',
passwordRequirementsNotMet:
'Ji kerema xwe hem\u00fb \u015fert\u00ean \u015f\u00eefre (password) bic\u00eeh b\u00eenin',
walletCreationFailed:
'Wallet \u00e7\u00eaneb\u00fb. Ji kerema xwe d\u00eesa bicerb\u00een\u00een',
preparing: 'T\u00ea amadekirin...',
creating: 'T\u00ea \u00e7\u00eakirin...',
meetPasswordRequirements: '\u015eert\u00ean \u015f\u00eefre (password) bic\u00eeh b\u00eenin',
backupTitle: 'Seed Phrase Pa\u015fguh Bike',
backupDescription:
"Ev 12 peyv wallet'\u00ea te ne. Wan li cihek\u00ee ewle bin\u00eev\u00eese!",
backupWarning:
"Gir\u00eeng: Ev peyvan ten\u00ea yek car t\u00eane xuyang kirin. Eger te ev peyv winda bikin, tu nikar\u00ee gih\u00ee\u015ftina wallet'\u00ea xwe bist\u00een\u00ee.",
copiedMnemonic: 'Hat kop\u00eekirin!',
copyMnemonic: 'Kop\u00ee Bike',
conditionWrittenDown: 'Min ev 12 peyv li cihek\u00ee ewle niv\u00eesandine',
conditionNeverShare: 'Ez f\u00eam dikim ku ez nikarim ev peyvan bi kes\u00ee re parve bikim',
conditionLossRisk:
"Ez f\u00eam dikim ku eger van peyvan winda bikim ez nikarim gih\u00ee\u015ftina wallet'\u00ea xwe bist\u00eenim",
acceptAllConditions: 'Hem\u00fb \u015fertan bipejirinIn',
verifyWords: 'Peyvan Verast Bike',
reset: 'Reset',
verifyDescription: 'Ji kerema xwe peyvan bi r\u00eaza rast bixin nav qut\u00eek\u00ea',
dropWordsHere: 'Peyvan li vir bixin...',
wrongOrder: 'R\u00eaza peyvan ne rast e. Ji kerema xwe d\u00eesa bicerb\u00een\u00een',
saving: 'T\u00ea tomark\u00eerin...',
verify: 'Verast Bike',
wordsCount: '{count}/12 peyv',
walletCreated: 'Wallet Hat \u00c7\u00eakirin!',
walletReady: "Wallet'\u00ea te amade ye",
yourAddress: 'Navnishana te',
getStarted: 'Dest P\u00ea Bike',
},
walletImport: {
title: 'Wallet Import Bike',
description: "Seed phrase'\u00ea wallet'\u00ea xwe y\u00ea hey\u00ee bin\u00eev\u00eese",
seedPhraseLabel: 'Seed Phrase (12 an 24 peyv)',
seedPhrasePlaceholder: 'Peyv\u00ean xwe bi valah\u00ee cuda bin\u00eev\u00eese...',
wordsCount: '{count} / 12 peyv',
newPassword: '\u015e\u00eefreya N\u00fb (New Password)',
seedPhraseInvalid: 'Seed phrase div\u00ea 12 an 24 peyv be',
passwordInvalid: '\u015e\u00eefre (password) ne derbasdar e',
passwordsMismatch: '\u015e\u00eefre (password) hev nagirin',
importFailed: 'Import neseret\u00ee',
importing: 'T\u00ea import kirin...',
importButton: 'Import Bike',
},
walletConnect: {
deleteTitle: 'Wallet J\u00ea Bibe?',
deleteDescription:
"Ev \u00e7alak\u00ee nay\u00ea pa\u015fveki\u015fandin. Eger seed phrase'\u00ea te tune be, tu nikar\u00ee gih\u00ee\u015ftina wallet'\u00ea xwe bist\u00een\u00ee.",
deleteButton: 'J\u00ea Bibe',
openWallet: 'Wallet Veke',
passwordLabel: '\u015e\u00eefre (Password)',
passwordPlaceholder: '\u015e\u00eefre (password) bin\u00eev\u00eese',
enterPassword: '\u015e\u00eefre (password) bin\u00eev\u00eese',
wrongPassword: '\u015e\u00eefre (password) \u00e7ewt e',
connecting: 'T\u00ea vekirin...',
connect: 'Connect',
deleteWalletLink: 'Wallet j\u00ea bibe',
},
deposit: {
title: 'USDT Depo Bike',
subtitle: 'Bo wUSDT li Asset Hub',
depositHistory: 'D\u00eeroka Depoyan',
noDeposits: 'H\u00een depo tune',
goBack: 'Vegere',
selectNetwork: 'Tor\u00ea Hilbij\u00eare',
recommended: 'P\u00ea\u015fniyar',
warning: 'Dikkkat!',
example: 'M\u00eenak: 10 USDT bi\u015f\u00eene \u2192 7 wUSDT werbigire ($3 masraf)',
acceptTrc20: 'Qeb\u00fbl dikim, bi TRC20 bi\u015f\u00eene',
important: 'Gir\u00eeng!',
minimum: 'K\u00eamtir\u00een: {amount} USDT',
memoRequired: 'Memo/Comment qada de koda xwe bin\u00eev\u00eese',
onlyUsdt: 'Ten\u00ea USDT bi\u015f\u00eene, token\u00ean din winda dibin',
trc20Fee: '$3 masraf d\u00ea ji m\u00eeqdara we b\u00ea k\u00eamkirin',
depositAddress: 'Navnishana Depoy\u00ea',
notAvailable: 'Amade n\u00eene',
memoLabel: 'Memo / Comment (P\u00caW\u00ceST)',
memoWarning:
'V\u00ea kod\u00ea di memo de bin\u00eev\u00eese, wek\u00ee din depoya te nay\u00ea nas kirin!',
uniqueAddress: 'Ev navnishana ten\u00ea ya te ye. Memo ne p\u00eaw\u00eest e.',
howToDeposit: '\u00c7awa Depo Bikim?',
stepCopyAddress: 'Navnishana kop\u00ee bike',
stepCopyMemo: 'Memo kod\u00ea kop\u00ee bike',
stepOpenTon: 'Telegram Wallet an c\u00eezdana xwe veke',
stepOpenPolkadot: 'Polkadot c\u00eezdana xwe veke',
stepOpenTrc20: 'TronLink an c\u00eezdana xwe veke',
stepSendUsdt: 'USDT bi\u015f\u00eene navnishana jor\u00een',
stepReceive: 'wUSDT d\u00ea di nav \u00e7end h\u00fbrdeman de li hesab\u00ea te be',
processingTime: 'Dema p\u00eavajoy\u00ea: ~1-5 h\u00fbrdem',
copyFailed: 'Kop\u00ee nekir',
statusPending: 'Li benda',
statusConfirming: 'T\u00ea pejirandin',
statusCompleted: 'Qediya',
statusFailed: 'Neseret\u00ee',
statusExpired: 'Dema w\u00ea derbas b\u00fb',
viewTx: 'TX bib\u00eene',
trc20FeeWarning:
'Mesrefa tora TRC20 bi qas\u00ee $3 ye. Em tora TON an Polkadot p\u00ea\u015fniyar dikin.',
},
p2p: {
title: 'P2P Dan\u00fbstandin',
subtitle: 'Dan\u00fbstandina kr\u00eeto di navbera kesan de',
firstTime: 'Cara yekem P2P bikar t\u00eenin?',
steps: [
'Li bi\u015fkoja j\u00ear\u00een bikirt\u00eenin da ku malpera web\u00ea vebike',
'Hesabek \u00e7\u00eabikin an t\u00eakevin',
'P\u00eav ajoya sazkirina P2P temam bikin',
'Pi\u015ft\u00ee sazkirin\u00ea, h\u00fbn dikarin rasterast bigih\u00eejin P2P',
],
note: 'Malpera web\u00ea di pencereyek n\u00fb de vedibe. P\u00eav ajoya qeydk\u00eerin\u00ea li wir temam bikin.',
button: 'P2P Veke',
},
update: {
newVersion: 'Guhertoya n\u00fb heye!',
description:
'Ji bo taybetmend\u00eey\u00ean n\u00fb \u00fb rastkirin\u00ean ewlehiy\u00ea n\u00fbve bike.',
later: 'Pa\u015f\u00ea',
updateNow: 'N\u00fbve bike',
},
social: {
followUs: 'Me bi\u015fop\u00eene',
stayConnected:
'Bi me re t\u00eak\u00eel\u00eey\u00ea ragire \u00fb n\u00fb\u00e7ey\u00ean her\u00ee daw\u00ee bist\u00eene!',
instagram: 'W\u00eane \u00fb Story',
tiktok: 'V\u00eedyoy\u00ean kurt',
snapchat: 'Snap bike!',
telegram: 'Kanala ferm\u00ee',
twitter: 'N\u00fb\u00e7ey\u00ean rojane',
youtube: 'V\u00eedyoy\u00ean me',
facebook: 'R\u00fbpela ferm\u00ee',
discord: 'Civaka me',
},
errorBoundary: {
title: 'Ti\u015ftek \u00e7ewt \u00e7\u00eab\u00fb',
description:
'Bibore, pirsgir\u00eakek tekn\u00eek\u00ee derket. Ji kerema xwe d\u00eesa bicerb\u00eene.',
retry: 'D\u00eesa bicerb\u00eene',
},
loadingScreen: {
loading: 'T\u00ea barkirin...',
},
};
export default krd;
+305
View File
@@ -0,0 +1,305 @@
import type { Translations } from '../types';
const tr: Translations = {
nav: {
announcements: 'Haberler',
forum: 'Forum',
rewards: 'Ödüller',
p2p: 'P2P',
wallet: 'Cüzdan',
},
common: {
back: 'Geri',
cancel: 'İptal',
continue: 'Devam',
close: 'Kapat',
copy: 'Kopyala',
copied: 'Kopyalandı!',
share: 'Paylaş',
refresh: 'Yenile',
retry: 'Tekrar dene',
loading: 'Yükleniyor...',
error: 'Hata',
anonymous: 'Anonim',
pinned: 'Sabitlenmiş',
locked: 'Kilitli',
trending: 'Trend',
},
announcements: {
title: 'Duyurular',
readMore: 'Devamını oku',
reactionAuthRequired: "Oy vermek için Telegram'da olmalısınız",
},
forum: {
title: 'Forum',
newTopic: 'Yeni Konu',
submitting: 'Gönderiliyor...',
publish: 'Yayınla',
category: 'Kategori',
topicTitle: 'Başlık',
topicTitlePlaceholder: 'Konu başlığı...',
content: 'İçerik',
contentPlaceholder: 'Konu içeriğini yazın...',
tagsLabel: 'Etiketler (virgülle ayırın)',
tagsPlaceholder: 'blockchain, kurd, pez...',
searchPlaceholder: 'Konu ara...',
sortRecent: 'Yeni',
sortPopular: 'Popüler',
sortReplies: 'Yanıtlar',
sortViews: 'Görüntüleme',
all: 'Tümü',
replies: 'Yanıtlar',
noRepliesYet: 'Henüz yanıt yok',
beFirstToReply: 'İlk yanıtı siz verin!',
replyPlaceholder: 'Yanıtınızı yazın...',
noTopicsFound: 'Konu bulunamadı',
changeFilters: 'Filtrelerinizi değiştirin',
createNewTopic: 'Yeni Konu Oluştur',
loginToVote: 'Oy vermek için giriş yapın',
voteError: 'Oy verme hatası',
writeReply: 'Lütfen yanıtınızı yazın',
topicLocked: 'Bu konu kilitli',
replyError: 'Yanıt gönderme hatası',
fillAllFields: 'Lütfen tüm alanları doldurun',
topicCreated: 'Konu oluşturuldu!',
topicCreateError: 'Konu oluşturma hatası',
},
rewards: {
title: 'Ödüller',
subtitle: 'Ödüller - Davet Sistemi',
connectWalletFirst: 'Davetlerinizi ve ödüllerinizi görmek için önce cüzdanınızı bağlayın.',
overview: 'Genel Bakış',
referral: 'Davet',
scores: 'Puanlar',
referralScore: 'Davet Puanı',
maxScore: 'Maksimum puan: 500',
referMoreTitle: 'Daha çok davet edin, daha çok kazanın!',
referMoreDescription:
'Getirdiğiniz her kişi için HEZ ve PEZ ödülleri alırsınız. Daha fazla davet = daha fazla ödül!',
kycApproved: 'KYC Onaylandı',
referrer: 'Davet Eden',
none: 'Yok',
invitedMe: 'Beni davet etti',
pendingReferral: 'Bekleyen davet',
completeKyc: "Davetini onaylamak için KYC'yi tamamlayın:",
inviteFriends: 'Arkadaşlarınızı davet edin',
yourLink: 'Bağlantınız',
copySuccess: 'Kopyalandı!',
copyLink: 'Kopyala',
shareLink: 'Paylaş',
scoreSystem: 'Puan sistemi',
activeStatus: 'Aktivite Durumu',
timeRemaining: 'Süre: {time} kaldı',
active: 'Aktif',
inactive: 'Pasif',
activeDescription: 'Aktif kalmak ve daha fazla ödül kazanmak için her 24 saatte bir tıklayın!',
youAreActive: 'Aktifsiniz!',
iAmActive: 'Aktifim!',
activatedAlert: 'Artık aktifsiniz! 24 saat sonra tekrar tıklayın.',
shareText: 'Pezkuwichain - Kürdistan Dijital Devleti! Bağlantımdan bize katılın:',
copyAlert: 'Kopyalandı',
referralCount: '{count} davet (KYC onaylı)',
noReferrals: 'Henüz davetiniz yok',
shareYourLink: 'Bağlantınızı paylaşın!',
trustScore: 'Güven Puanı',
rank: 'Sıralama: {rating}',
citizen: 'Vatandaş',
staking: 'Staking',
stakingNotStarted: 'Başlamadı',
stakingCountedInTrust: 'Güven puanına dahil',
people: '{count} kişi',
tiki: 'Tiki',
nftRole: 'NFT Rolü',
education: 'Eğitim',
reading: 'Okuma',
stakingRewards: 'Staking Ödülleri',
totalRewards: 'Toplam alınan ödüller',
recentRewards: 'Son ödüller',
noRewardsYet: 'Henüz kaydedilmiş ödül yok',
scoreFormula: 'Puan Formülü',
stakingZeroWarning: 'Staking 0 ise Güven puanı da 0 olur. Önce stake yapın!',
refreshScores: 'Puanları Yenile',
points: 'puan',
},
wallet: {
title: 'Cüzdan',
authFailed: 'Kimlik Doğrulama Başarısız',
authDescription: 'Lütfen bu uygulamayı Telegram içinden açtığınızdan emin olun',
retry: 'Tekrar Dene',
},
walletSetup: {
officialWallet: 'Resmi Pezkuwichain cüzdanı',
createNew: 'Yeni Cüzdan Oluştur',
createNewDesc: 'Kurtarma kelimeleriyle yeni bir cüzdan oluşturun',
importWallet: 'Cüzdan İçe Aktar',
importWalletDesc: 'Mevcut kurtarma kelimelerinizi kullanın',
securityNote:
'Cüzdanınız cihazınızda güvenli bir şekilde saklanır. Anahtarlarınıza asla erişimimiz yoktur.',
},
walletCreate: {
setPassword: 'Şifre Belirle',
passwordDescription: 'Bu şifre cüzdanınızın kilidini açmak için kullanılacaktır',
passwordLabel: 'Şifre',
passwordPlaceholder: 'En az 12 karakter',
confirmPasswordLabel: 'Şifreyi Onayla',
confirmPasswordPlaceholder: 'Şifrenizi tekrar girin',
passwordRequirements: 'Şifre Gereksinimleri:',
ruleMinLength: 'En az 12 karakter',
ruleLowercase: 'En az 1 küçük harf (a-z)',
ruleUppercase: 'En az 1 büyük harf (A-Z)',
ruleNumber: 'En az 1 rakam (0-9)',
ruleSpecialChar: 'En az 1 özel karakter (!@#$%...)',
rulePasswordsMatch: 'Şifreler eşleşiyor',
walletServiceNotReady: 'Cüzdan hizmeti hazır değil. Lütfen bekleyin.',
passwordRequirementsNotMet: 'Lütfen tüm şifre gereksinimlerini karşılayın',
walletCreationFailed: 'Cüzdan oluşturma başarısız. Lütfen tekrar deneyin',
preparing: 'Hazırlanıyor...',
creating: 'Oluşturuluyor...',
meetPasswordRequirements: 'Şifre gereksinimlerini karşılayın',
backupTitle: 'Kurtarma Kelimelerini Yedekle',
backupDescription: 'Bu 12 kelime cüzdanınızdır. Güvenli bir yere yazın!',
backupWarning:
'Önemli: Bu kelimeler yalnızca bir kez gösterilir. Kaybederseniz cüzdanınıza erişemezsiniz.',
copiedMnemonic: 'Kopyalandı!',
copyMnemonic: 'Kopyala',
conditionWrittenDown: 'Bu 12 kelimeyi güvenli bir yere yazdım',
conditionNeverShare: 'Bu kelimeleri asla kimseyle paylaşmamam gerektiğini anlıyorum',
conditionLossRisk: 'Bu kelimeleri kaybedersem cüzdanıma erişemeyeceğimi anlıyorum',
acceptAllConditions: 'Tüm koşulları kabul et',
verifyWords: 'Kelimeleri Doğrula',
reset: 'Sıfırla',
verifyDescription: 'Lütfen kelimeleri doğru sıraya dizin',
dropWordsHere: 'Kelimeleri buraya yerleştirin...',
wrongOrder: 'Yanlış kelime sırası. Lütfen tekrar deneyin',
saving: 'Kaydediliyor...',
verify: 'Doğrula',
wordsCount: '{count}/12 kelime',
walletCreated: 'Cüzdan Oluşturuldu!',
walletReady: 'Cüzdanınız hazır',
yourAddress: 'Adresiniz',
getStarted: 'Başlayın',
},
walletImport: {
title: 'Cüzdan İçe Aktar',
description: 'Mevcut cüzdanınızın kurtarma kelimelerini girin',
seedPhraseLabel: 'Kurtarma Kelimeleri (12 veya 24 kelime)',
seedPhrasePlaceholder: 'Kelimeleri boşlukla ayırarak girin...',
wordsCount: '{count} / 12 kelime',
newPassword: 'Yeni Şifre',
seedPhraseInvalid: 'Kurtarma kelimeleri 12 veya 24 kelime olmalıdır',
passwordInvalid: 'Şifre geçersiz',
passwordsMismatch: 'Şifreler eşleşmiyor',
importFailed: 'İçe aktarma başarısız',
importing: 'İçe aktarılıyor...',
importButton: 'İçe Aktar',
},
walletConnect: {
deleteTitle: 'Cüzdan Silinsin mi?',
deleteDescription:
'Bu işlem geri alınamaz. Kurtarma kelimeleriniz yoksa cüzdanınıza erişemezsiniz.',
deleteButton: 'Sil',
openWallet: 'Cüzdanı Aç',
passwordLabel: 'Şifre',
passwordPlaceholder: 'Şifrenizi girin',
enterPassword: 'Şifrenizi girin',
wrongPassword: 'Yanlış şifre',
connecting: 'Açılıyor...',
connect: 'Bağlan',
deleteWalletLink: 'Cüzdanı sil',
},
deposit: {
title: 'USDT Yatır',
subtitle: 'Asset Hub üzerinde wUSDT için',
depositHistory: 'Yatırma Geçmişi',
noDeposits: 'Henüz yatırma yok',
goBack: 'Geri dön',
selectNetwork: 'Ağ Seçin',
recommended: 'Önerilen',
warning: 'Uyarı!',
example: 'Örnek: 10 USDT gönderin \u2192 7 wUSDT alın (3$ komisyon)',
acceptTrc20: 'Kabul ediyorum, TRC20 ile gönder',
important: 'Önemli!',
minimum: 'Minimum: {amount} USDT',
memoRequired: 'Memo/Açıklama alanına kodunuzu yazın',
onlyUsdt: 'Yalnızca USDT gönderin, diğer tokenlar kaybolur',
trc20Fee: 'Tutarınızdan 3$ komisyon düşülecektir',
depositAddress: 'Yatırma Adresi',
notAvailable: 'Mevcut değil',
memoLabel: 'Memo / Açıklama (ZORUNLU)',
memoWarning: 'Bu kodu memo alanına yazın, aksi takdirde yatırmanız tanınmaz!',
uniqueAddress: 'Bu adres size özeldir. Memo gerekmez.',
howToDeposit: 'Nasıl Yatırılır?',
stepCopyAddress: 'Adresi kopyalayın',
stepCopyMemo: 'Memo kodunu kopyalayın',
stepOpenTon: 'Telegram Cüzdanı veya cüzdanınızı açın',
stepOpenPolkadot: 'Polkadot cüzdanınızı açın',
stepOpenTrc20: 'TronLink veya cüzdanınızı açın',
stepSendUsdt: 'Yukarıdaki adrese USDT gönderin',
stepReceive: 'wUSDT birkaç dakika içinde hesabınızda olacak',
processingTime: 'İşlem süresi: ~1-5 dakika',
copyFailed: 'Kopyalama başarısız',
statusPending: 'Beklemede',
statusConfirming: 'Onaylanıyor',
statusCompleted: 'Tamamlandı',
statusFailed: 'Başarısız',
statusExpired: 'Süresi Doldu',
viewTx: 'TX Görüntüle',
trc20FeeWarning: "TRC20 ağ komisyonu yaklaşık 3$'dır. TON veya Polkadot ağını öneririz.",
},
p2p: {
title: 'P2P Borsa',
subtitle: 'Eşler arası kripto ticareti',
firstTime: "P2P'yi ilk kez mi kullanıyorsunuz?",
steps: [
'Web uygulamasını açmak için aşağıdaki düğmeye tıklayın',
'Hesap oluşturun veya giriş yapın',
'P2P kurulum sürecini tamamlayın',
"Kurulumdan sonra P2P'ye doğrudan erişebilirsiniz",
],
note: 'Web uygulaması yeni bir pencerede açılacak. Kayıt işlemini orada tamamlayın.',
button: 'P2P Platformunu Aç',
},
update: {
newVersion: 'Yeni sürüm mevcut!',
description: 'Yeni özellikler ve güvenlik düzeltmeleri için güncelleyin.',
later: 'Sonra',
updateNow: 'Güncelle',
},
social: {
followUs: 'Bizi takip edin',
stayConnected: 'Bağlantıda kalın ve en son haberleri alın!',
instagram: 'Fotoğraflar ve Hikayeler',
tiktok: 'Kısa videolar',
snapchat: 'Snap gönderin!',
telegram: 'Resmi kanal',
twitter: 'Günlük haberler',
youtube: 'Videolarımız',
facebook: 'Resmi sayfa',
discord: 'Topluluğumuz',
},
errorBoundary: {
title: 'Bir şeyler yanlış gitti',
description: 'Üzgünüz, teknik bir hata oluştu. Lütfen tekrar deneyin.',
retry: 'Tekrar dene',
},
loadingScreen: {
loading: 'Yükleniyor...',
},
};
export default tr;
+323
View File
@@ -0,0 +1,323 @@
export interface Translations {
// Navigation
nav: {
announcements: string;
forum: string;
rewards: string;
p2p: string;
wallet: string;
};
// Common
common: {
back: string;
cancel: string;
continue: string;
close: string;
copy: string;
copied: string;
share: string;
refresh: string;
retry: string;
loading: string;
error: string;
anonymous: string;
pinned: string;
locked: string;
trending: string;
};
// Announcements section
announcements: {
title: string;
readMore: string;
reactionAuthRequired: string;
};
// Forum section
forum: {
title: string;
newTopic: string;
submitting: string;
publish: string;
category: string;
topicTitle: string;
topicTitlePlaceholder: string;
content: string;
contentPlaceholder: string;
tagsLabel: string;
tagsPlaceholder: string;
searchPlaceholder: string;
sortRecent: string;
sortPopular: string;
sortReplies: string;
sortViews: string;
all: string;
replies: string;
noRepliesYet: string;
beFirstToReply: string;
replyPlaceholder: string;
noTopicsFound: string;
changeFilters: string;
createNewTopic: string;
loginToVote: string;
voteError: string;
writeReply: string;
topicLocked: string;
replyError: string;
fillAllFields: string;
topicCreated: string;
topicCreateError: string;
};
// Rewards section
rewards: {
title: string;
subtitle: string;
connectWalletFirst: string;
overview: string;
referral: string;
scores: string;
referralScore: string;
maxScore: string;
referMoreTitle: string;
referMoreDescription: string;
kycApproved: string;
referrer: string;
none: string;
invitedMe: string;
pendingReferral: string;
completeKyc: string;
inviteFriends: string;
yourLink: string;
copySuccess: string;
copyLink: string;
shareLink: string;
scoreSystem: string;
activeStatus: string;
timeRemaining: string;
active: string;
inactive: string;
activeDescription: string;
youAreActive: string;
iAmActive: string;
activatedAlert: string;
shareText: string;
copyAlert: string;
referralCount: string;
noReferrals: string;
shareYourLink: string;
trustScore: string;
rank: string;
citizen: string;
staking: string;
stakingNotStarted: string;
stakingCountedInTrust: string;
people: string;
tiki: string;
nftRole: string;
education: string;
reading: string;
stakingRewards: string;
totalRewards: string;
recentRewards: string;
noRewardsYet: string;
scoreFormula: string;
stakingZeroWarning: string;
refreshScores: string;
points: string;
};
// Wallet section
wallet: {
title: string;
authFailed: string;
authDescription: string;
retry: string;
};
// Wallet Setup
walletSetup: {
officialWallet: string;
createNew: string;
createNewDesc: string;
importWallet: string;
importWalletDesc: string;
securityNote: string;
};
// Wallet Create
walletCreate: {
setPassword: string;
passwordDescription: string;
passwordLabel: string;
passwordPlaceholder: string;
confirmPasswordLabel: string;
confirmPasswordPlaceholder: string;
passwordRequirements: string;
ruleMinLength: string;
ruleLowercase: string;
ruleUppercase: string;
ruleNumber: string;
ruleSpecialChar: string;
rulePasswordsMatch: string;
walletServiceNotReady: string;
passwordRequirementsNotMet: string;
walletCreationFailed: string;
preparing: string;
creating: string;
meetPasswordRequirements: string;
backupTitle: string;
backupDescription: string;
backupWarning: string;
copiedMnemonic: string;
copyMnemonic: string;
conditionWrittenDown: string;
conditionNeverShare: string;
conditionLossRisk: string;
acceptAllConditions: string;
verifyWords: string;
reset: string;
verifyDescription: string;
dropWordsHere: string;
wrongOrder: string;
saving: string;
verify: string;
wordsCount: string;
walletCreated: string;
walletReady: string;
yourAddress: string;
getStarted: string;
};
// Wallet Import
walletImport: {
title: string;
description: string;
seedPhraseLabel: string;
seedPhrasePlaceholder: string;
wordsCount: string;
newPassword: string;
seedPhraseInvalid: string;
passwordInvalid: string;
passwordsMismatch: string;
importFailed: string;
importing: string;
importButton: string;
};
// Wallet Connect
walletConnect: {
deleteTitle: string;
deleteDescription: string;
deleteButton: string;
openWallet: string;
passwordLabel: string;
passwordPlaceholder: string;
enterPassword: string;
wrongPassword: string;
connecting: string;
connect: string;
deleteWalletLink: string;
};
// Deposit USDT Modal
deposit: {
title: string;
subtitle: string;
depositHistory: string;
noDeposits: string;
goBack: string;
selectNetwork: string;
recommended: string;
warning: string;
example: string;
acceptTrc20: string;
important: string;
minimum: string;
memoRequired: string;
onlyUsdt: string;
trc20Fee: string;
depositAddress: string;
notAvailable: string;
memoLabel: string;
memoWarning: string;
uniqueAddress: string;
howToDeposit: string;
stepCopyAddress: string;
stepCopyMemo: string;
stepOpenTon: string;
stepOpenPolkadot: string;
stepOpenTrc20: string;
stepSendUsdt: string;
stepReceive: string;
processingTime: string;
copyFailed: string;
statusPending: string;
statusConfirming: string;
statusCompleted: string;
statusFailed: string;
statusExpired: string;
viewTx: string;
trc20FeeWarning: string;
};
// P2P Modal
p2p: {
title: string;
subtitle: string;
firstTime: string;
steps: string[];
note: string;
button: string;
};
// Update Notification
update: {
newVersion: string;
description: string;
later: string;
updateNow: string;
};
// Social Links
social: {
followUs: string;
stayConnected: string;
instagram: string;
tiktok: string;
snapchat: string;
telegram: string;
twitter: string;
youtube: string;
facebook: string;
discord: string;
};
// Error Boundary
errorBoundary: {
title: string;
description: string;
retry: string;
};
// Loading Screen
loadingScreen: {
loading: string;
};
}
export type LanguageCode = 'krd' | 'en' | 'tr' | 'ckb' | 'fa' | 'ar';
export const VALID_LANGS: LanguageCode[] = ['krd', 'en', 'tr', 'ckb', 'fa', 'ar'];
export const DEFAULT_LANG: LanguageCode = 'krd';
export const RTL_LANGUAGES: LanguageCode[] = ['ckb', 'fa', 'ar'];
export const LANGUAGE_NAMES: Record<LanguageCode, string> = {
krd: 'Kurmancî',
en: 'English',
tr: 'Türkçe',
ckb: 'سۆرانی',
fa: 'فارسی',
ar: 'العربية',
};
+12 -9
View File
@@ -5,6 +5,7 @@ import { AuthProvider } from './contexts/AuthContext';
import { WalletProvider } from './contexts/WalletContext';
import { ReferralProvider } from './contexts/ReferralContext';
import { ErrorBoundary } from './components/ErrorBoundary';
import { LanguageProvider } from './i18n';
import App from './App';
import './index.css';
@@ -42,15 +43,17 @@ if (!rootElement) {
createRoot(rootElement).render(
<StrictMode>
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<WalletProvider>
<ReferralProvider>
<App />
</ReferralProvider>
</WalletProvider>
</AuthProvider>
</QueryClientProvider>
<LanguageProvider>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<WalletProvider>
<ReferralProvider>
<App />
</ReferralProvider>
</WalletProvider>
</AuthProvider>
</QueryClientProvider>
</LanguageProvider>
</ErrorBoundary>
</StrictMode>
);
+6 -4
View File
@@ -10,17 +10,19 @@ import {
import { cn, formatDate, formatNumber } from '@/lib/utils';
import { useTelegram } from '@/hooks/useTelegram';
import { useAnnouncements, useAnnouncementReaction } from '@/hooks/useSupabase';
import { useTranslation } from '@/i18n';
export function AnnouncementsSection() {
const { hapticImpact, hapticNotification, openLink } = useTelegram();
const { data: announcements, isLoading, refetch, isRefetching } = useAnnouncements();
const reactionMutation = useAnnouncementReaction();
const { t } = useTranslation();
const handleReaction = (id: string, reaction: 'like' | 'dislike') => {
if (!window.Telegram?.WebApp?.initData) {
hapticNotification('error');
if (window.Telegram?.WebApp) {
window.Telegram.WebApp.showAlert('Ji bo dengdanê divê tu di Telegramê de bî');
window.Telegram.WebApp.showAlert(t('announcements.reactionAuthRequired'));
}
return;
}
@@ -31,7 +33,7 @@ export function AnnouncementsSection() {
onSuccess: () => hapticNotification('success'),
onError: (err) => {
hapticNotification('error');
window.Telegram?.WebApp?.showAlert(err.message || 'Çewtî');
window.Telegram?.WebApp?.showAlert(err.message || t('common.error'));
},
}
);
@@ -51,7 +53,7 @@ export function AnnouncementsSection() {
<div className="w-8 h-8 rounded-lg bg-primary/20 flex items-center justify-center">
<Megaphone className="w-4 h-4 text-primary" />
</div>
<h1 className="text-lg font-semibold">Ragihandin</h1>
<h1 className="text-lg font-semibold">{t('announcements.title')}</h1>
</div>
<button
onClick={handleRefresh}
@@ -113,7 +115,7 @@ export function AnnouncementsSection() {
className="flex items-center gap-1.5 text-sm text-primary mb-3 hover:underline"
>
<ExternalLink className="w-3.5 h-3.5" />
Zêdetir bixwîne
{t('announcements.readMore')}
</button>
)}
+47 -41
View File
@@ -31,12 +31,14 @@ import { useTelegram } from '@/hooks/useTelegram';
import { useAuth } from '@/contexts/AuthContext';
import { useForum, type ForumDiscussion, type ForumReply } from '@/hooks/useForum';
import { formatDistanceToNow } from 'date-fns';
import { useTranslation } from '@/i18n';
type SortBy = 'recent' | 'popular' | 'replies' | 'views';
export function ForumSection() {
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
const { user: authUser } = useAuth();
const { t } = useTranslation();
// Use authenticated user ID from backend, not initDataUnsafe
const userId = authUser?.telegram_id?.toString() || '';
const userName = authUser?.first_name || 'Telegram User';
@@ -116,7 +118,7 @@ export function ForumSection() {
const handleVoteDiscussion = async (voteType: 'upvote' | 'downvote') => {
if (!selectedDiscussion || !userId) {
showAlert('Ji bo dengdanê têkeve');
showAlert(t('forum.loginToVote'));
return;
}
@@ -132,13 +134,13 @@ export function ForumSection() {
}
} catch {
hapticNotification('error');
showAlert('Çewtî di dengdanê de');
showAlert(t('forum.voteError'));
}
};
const handleVoteReply = async (replyId: string, voteType: 'upvote' | 'downvote') => {
if (!userId) {
showAlert('Ji bo dengdanê têkeve');
showAlert(t('forum.loginToVote'));
return;
}
@@ -159,12 +161,12 @@ export function ForumSection() {
const handleSubmitReply = async () => {
if (!selectedDiscussion || !replyText.trim() || !userId) {
showAlert('Ji kerema xwe bersiva xwe binivîse');
showAlert(t('forum.writeReply'));
return;
}
if (selectedDiscussion.is_locked) {
showAlert('Ev mijar kilîtkirî ye');
showAlert(t('forum.topicLocked'));
return;
}
@@ -187,7 +189,7 @@ export function ForumSection() {
setReplies(loadedReplies);
} catch {
hapticNotification('error');
showAlert('Çewtî di şandina bersivê de');
showAlert(t('forum.replyError'));
} finally {
setSubmittingReply(false);
}
@@ -195,7 +197,7 @@ export function ForumSection() {
const handleSubmitDiscussion = async () => {
if (!newTitle.trim() || !newContent.trim() || !newCategory || !userId) {
showAlert('Ji kerema xwe hemû qadan tije bike');
showAlert(t('forum.fillAllFields'));
return;
}
@@ -205,8 +207,8 @@ export function ForumSection() {
try {
const tags = newTags
.split(',')
.map((t) => t.trim())
.filter((t) => t.length > 0);
.map((tag) => tag.trim())
.filter((tag) => tag.length > 0);
await createDiscussion({
title: newTitle.trim(),
@@ -218,11 +220,11 @@ export function ForumSection() {
});
hapticNotification('success');
showAlert('Mijar hat afirandin!');
showAlert(t('forum.topicCreated'));
handleCloseCreate();
} catch {
hapticNotification('error');
showAlert('Çewtî di afirandina mijarê de');
showAlert(t('forum.topicCreateError'));
} finally {
setSubmittingDiscussion(false);
}
@@ -291,7 +293,7 @@ export function ForumSection() {
>
<X className="w-5 h-5" />
</button>
<h1 className="text-lg font-semibold">Mijara </h1>
<h1 className="text-lg font-semibold">{t('forum.newTopic')}</h1>
</div>
<button
onClick={handleSubmitDiscussion}
@@ -303,7 +305,7 @@ export function ForumSection() {
: 'bg-primary text-primary-foreground'
)}
>
{submittingDiscussion ? 'Tê şandin...' : 'Biweşîne'}
{submittingDiscussion ? t('forum.submitting') : t('forum.publish')}
</button>
</div>
</header>
@@ -311,7 +313,9 @@ export function ForumSection() {
<div className="flex-1 overflow-y-auto hide-scrollbar p-4 space-y-4">
{/* Category */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Kategorî</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('forum.category')}
</label>
<div className="flex gap-2 flex-wrap">
{categories.map((cat) => (
<button
@@ -336,12 +340,14 @@ export function ForumSection() {
{/* Title */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Sernav</label>
<label className="text-sm text-muted-foreground mb-2 block">
{t('forum.topicTitle')}
</label>
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
placeholder="Navê mijarê..."
placeholder={t('forum.topicTitlePlaceholder')}
className="w-full px-4 py-3 bg-secondary rounded-lg text-foreground"
maxLength={200}
/>
@@ -349,11 +355,11 @@ export function ForumSection() {
{/* Content */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Naverok</label>
<label className="text-sm text-muted-foreground mb-2 block">{t('forum.content')}</label>
<textarea
value={newContent}
onChange={(e) => setNewContent(e.target.value)}
placeholder="Naveroka mijarê binivîse..."
placeholder={t('forum.contentPlaceholder')}
className="w-full px-4 py-3 bg-secondary rounded-lg text-foreground min-h-[200px] resize-none"
/>
</div>
@@ -361,13 +367,13 @@ export function ForumSection() {
{/* Tags */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">
Etîket (bi virgulê cuda bike)
{t('forum.tagsLabel')}
</label>
<input
type="text"
value={newTags}
onChange={(e) => setNewTags(e.target.value)}
placeholder="blockchain, kurd, pez..."
placeholder={t('forum.tagsPlaceholder')}
className="w-full px-4 py-3 bg-secondary rounded-lg text-foreground"
/>
</div>
@@ -395,13 +401,13 @@ export function ForumSection() {
{selectedDiscussion.is_pinned && (
<span className="inline-flex items-center gap-1 text-xs bg-yellow-500/20 text-yellow-400 px-2 py-1 rounded-full">
<Pin className="w-3 h-3" />
Pinned
{t('common.pinned')}
</span>
)}
{selectedDiscussion.is_locked && (
<span className="inline-flex items-center gap-1 text-xs bg-red-500/20 text-red-400 px-2 py-1 rounded-full">
<Lock className="w-3 h-3" />
Kilîtkirî
{t('common.locked')}
</span>
)}
{selectedDiscussion.category && (
@@ -419,7 +425,7 @@ export function ForumSection() {
</div>
<div>
<p className="text-sm font-medium">
{selectedDiscussion.author_name || 'Anonymous'}
{selectedDiscussion.author_name || t('common.anonymous')}
</p>
<p className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(selectedDiscussion.created_at), {
@@ -499,7 +505,7 @@ export function ForumSection() {
<div className="mb-4">
<h3 className="text-sm font-semibold text-muted-foreground mb-3 flex items-center gap-2">
<MessageSquare className="w-4 h-4" />
Bersiv ({replies.length})
{t('forum.replies')} ({replies.length})
</h3>
{loadingReplies ? (
@@ -514,8 +520,8 @@ export function ForumSection() {
) : replies.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
<MessageSquare className="w-8 h-8 mx-auto mb-2 opacity-50" />
<p className="text-sm">Hêj bersiv tune ye</p>
<p className="text-xs">Yekemîn bersivê tu bide!</p>
<p className="text-sm">{t('forum.noRepliesYet')}</p>
<p className="text-xs">{t('forum.beFirstToReply')}</p>
</div>
) : (
<div className="space-y-3">
@@ -529,7 +535,7 @@ export function ForumSection() {
{reply.author_name?.charAt(0) || 'A'}
</div>
<span className="text-sm font-medium">
{reply.author_name || 'Anonymous'}
{reply.author_name || t('common.anonymous')}
</span>
<span className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(reply.created_at), { addSuffix: true })}
@@ -579,7 +585,7 @@ export function ForumSection() {
type="text"
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
placeholder="Bersiva xwe binivîse..."
placeholder={t('forum.replyPlaceholder')}
className="flex-1 px-4 py-2.5 bg-secondary rounded-lg text-sm"
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
@@ -616,7 +622,7 @@ export function ForumSection() {
<div className="w-8 h-8 rounded-lg bg-blue-500/20 flex items-center justify-center">
<MessageCircle className="w-4 h-4 text-blue-400" />
</div>
<h1 className="text-lg font-semibold">Forum</h1>
<h1 className="text-lg font-semibold">{t('forum.title')}</h1>
<span className="text-xs text-muted-foreground">({discussions.length})</span>
</div>
<div className="flex items-center gap-1">
@@ -648,7 +654,7 @@ export function ForumSection() {
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Mijar bigere..."
placeholder={t('forum.searchPlaceholder')}
className="w-full pl-9 pr-4 py-2 bg-secondary rounded-lg text-sm"
/>
</div>
@@ -656,10 +662,10 @@ export function ForumSection() {
{/* Sort Tabs */}
<div className="flex gap-1 bg-secondary/50 rounded-lg p-1">
{[
{ id: 'recent' as SortBy, icon: Clock, label: 'Nû' },
{ id: 'popular' as SortBy, icon: TrendingUp, label: 'Populer' },
{ id: 'replies' as SortBy, icon: MessageSquare, label: 'Bersiv' },
{ id: 'views' as SortBy, icon: Eye, label: 'Dîtin' },
{ id: 'recent' as SortBy, icon: Clock, label: t('forum.sortRecent') },
{ id: 'popular' as SortBy, icon: TrendingUp, label: t('forum.sortPopular') },
{ id: 'replies' as SortBy, icon: MessageSquare, label: t('forum.sortReplies') },
{ id: 'views' as SortBy, icon: Eye, label: t('forum.sortViews') },
].map(({ id, icon: Icon, label }) => (
<button
key={id}
@@ -718,7 +724,7 @@ export function ForumSection() {
: 'bg-secondary text-muted-foreground'
)}
>
Hemû
{t('forum.all')}
</button>
{categories.map((category) => (
<button
@@ -755,14 +761,14 @@ export function ForumSection() {
) : filteredDiscussions.length === 0 ? (
<div className="flex flex-col items-center justify-center h-64 p-8 text-center">
<MessageCircle className="w-12 h-12 text-muted-foreground/50 mb-4" />
<p className="text-muted-foreground">Mijar nehat dîtin</p>
<p className="text-sm text-muted-foreground/70 mb-4">Filterên xwe biguhêre</p>
<p className="text-muted-foreground">{t('forum.noTopicsFound')}</p>
<p className="text-sm text-muted-foreground/70 mb-4">{t('forum.changeFilters')}</p>
<button
onClick={handleOpenCreate}
className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm"
>
<Plus className="w-4 h-4" />
Mijara Biafirîne
{t('forum.createNewTopic')}
</button>
</div>
) : (
@@ -778,7 +784,7 @@ export function ForumSection() {
{discussion.is_pinned && (
<span className="inline-flex items-center gap-1 text-[10px] bg-yellow-500/20 text-yellow-400 px-1.5 py-0.5 rounded">
<Pin className="w-2.5 h-2.5" />
Pinned
{t('common.pinned')}
</span>
)}
{discussion.is_locked && (
@@ -794,7 +800,7 @@ export function ForumSection() {
{(discussion.upvotes || 0) > 10 && (
<span className="inline-flex items-center gap-1 text-[10px] bg-orange-500/20 text-orange-400 px-1.5 py-0.5 rounded">
<Flame className="w-2.5 h-2.5" />
Trending
{t('common.trending')}
</span>
)}
</div>
@@ -817,7 +823,7 @@ export function ForumSection() {
{/* Meta */}
<div className="flex items-center gap-3 text-xs text-muted-foreground flex-wrap">
<span>{discussion.author_name || 'Anonymous'}</span>
<span>{discussion.author_name || t('common.anonymous')}</span>
<span>
{formatDistanceToNow(new Date(discussion.last_activity_at), {
addSuffix: true,
+77 -70
View File
@@ -29,6 +29,7 @@ import { useAuth } from '@/contexts/AuthContext';
import { useReferral } from '@/contexts/ReferralContext';
import { useWallet } from '@/contexts/WalletContext';
import { SocialLinks } from '@/components/SocialLinks';
import { useTranslation } from '@/i18n';
import {
getAllScores,
getStakingScoreStatus,
@@ -54,6 +55,7 @@ export function RewardsSection() {
const { user: authUser } = useAuth();
const { stats, myReferrals, loading, refreshStats } = useReferral();
const { isConnected, address, peopleApi } = useWallet();
const { t } = useTranslation();
const [copied, setCopied] = useState(false);
const [activeTab, setActiveTab] = useState<'overview' | 'referrals' | 'scores'>('overview');
@@ -136,7 +138,7 @@ export function RewardsSection() {
localStorage.setItem(ACTIVITY_STORAGE_KEY, Date.now().toString());
setIsActive(true);
setTimeRemaining('24s 0d');
showAlert('Tu niha aktîv î! 24 saet paşê dîsa bikirtîne.');
showAlert(t('rewards.activatedAlert'));
};
// Telegram referral link (for sharing) - use authenticated user ID
@@ -151,16 +153,13 @@ export function RewardsSection() {
hapticNotification('success');
setTimeout(() => setCopied(false), 2000);
} catch {
showAlert('Kopî bû');
showAlert(t('rewards.copyAlert'));
}
};
const handleShare = () => {
hapticImpact('medium');
shareUrl(
referralLink,
'Pezkuwichain - Dewleta Dîjîtal a Kurd! Bi lînka min ve tev li me bibe:'
);
shareUrl(referralLink, t('rewards.shareText'));
};
const handleRefresh = () => {
@@ -180,10 +179,8 @@ export function RewardsSection() {
return (
<div className="flex flex-col h-full items-center justify-center p-8 text-center">
<Gift className="w-16 h-16 text-purple-400 mb-4" />
<h2 className="text-xl font-semibold mb-2">Xelat - Referral System</h2>
<p className="text-muted-foreground mb-4">
Ji bo dîtina referral û xelatên xwe, berî her tiştî cîzdanê xwe girêde.
</p>
<h2 className="text-xl font-semibold mb-2">{t('rewards.subtitle')}</h2>
<p className="text-muted-foreground mb-4">{t('rewards.connectWalletFirst')}</p>
</div>
);
}
@@ -197,7 +194,7 @@ export function RewardsSection() {
<div className="w-8 h-8 rounded-lg bg-purple-500/20 flex items-center justify-center">
<Gift className="w-4 h-4 text-purple-400" />
</div>
<h1 className="text-lg font-semibold">Xelat</h1>
<h1 className="text-lg font-semibold">{t('rewards.title')}</h1>
</div>
<button
onClick={handleRefresh}
@@ -213,9 +210,9 @@ export function RewardsSection() {
{/* Tabs */}
<div className="flex gap-1 bg-secondary/50 rounded-lg p-1">
{[
{ id: 'overview' as const, label: 'Geşbîn' },
{ id: 'referrals' as const, label: 'Referral' },
{ id: 'scores' as const, label: 'Xal' },
{ id: 'overview' as const, label: t('rewards.overview') },
{ id: 'referrals' as const, label: t('rewards.referral') },
{ id: 'scores' as const, label: t('rewards.scores') },
].map(({ id, label }) => (
<button
key={id}
@@ -255,7 +252,7 @@ export function RewardsSection() {
<div className="bg-gradient-to-br from-purple-600 to-pink-600 rounded-2xl p-4 text-white">
<div className="flex items-center justify-between mb-4">
<div>
<p className="text-purple-100 text-sm">Pûana Referral</p>
<p className="text-purple-100 text-sm">{t('rewards.referralScore')}</p>
<p className="text-4xl font-bold">{stats?.referralScore ?? 0}</p>
</div>
<div className="w-16 h-16 rounded-full bg-white/20 flex items-center justify-center">
@@ -264,7 +261,7 @@ export function RewardsSection() {
</div>
<div className="flex items-center gap-2 text-sm text-purple-100">
<TrendingUp className="w-4 h-4" />
<span>Max pûan: 500</span>
<span>{t('rewards.maxScore')}</span>
</div>
</div>
@@ -276,10 +273,10 @@ export function RewardsSection() {
</div>
<div>
<h4 className="font-semibold text-amber-100 mb-1">
زیاتر ڕیفەر بکە، زیاتر قازانج بکە!
{t('rewards.referMoreTitle')}
</h4>
<p className="text-sm text-amber-200/80">
هەر کەسێک بهێنیت، HEZ و PEZ وەک خەڵات وەردەگریت. زیاتر ڕیفەر = زیاتر خەڵات!
{t('rewards.referMoreDescription')}
</p>
</div>
</div>
@@ -290,27 +287,27 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Users className="w-4 h-4 text-green-400" />
<span className="text-xs text-muted-foreground">Referral</span>
<span className="text-xs text-muted-foreground">{t('rewards.referral')}</span>
</div>
<p className="text-2xl font-bold text-foreground">
{stats?.referralCount ?? 0}
</p>
<p className="text-xs text-muted-foreground">KYC pejirandî</p>
<p className="text-xs text-muted-foreground">{t('rewards.kycApproved')}</p>
</div>
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Award className="w-4 h-4 text-blue-400" />
<span className="text-xs text-muted-foreground">Referrer</span>
<span className="text-xs text-muted-foreground">{t('rewards.referrer')}</span>
</div>
{stats?.whoInvitedMe ? (
<p className="text-sm font-mono text-foreground truncate">
{formatAddress(stats.whoInvitedMe, 6)}
</p>
) : (
<p className="text-sm text-muted-foreground">Tune</p>
<p className="text-sm text-muted-foreground">{t('rewards.none')}</p>
)}
<p className="text-xs text-muted-foreground">Min vexwand</p>
<p className="text-xs text-muted-foreground">{t('rewards.invitedMe')}</p>
</div>
</div>
@@ -322,9 +319,11 @@ export function RewardsSection() {
<Award className="w-5 h-5 text-blue-400" />
</div>
<div className="flex-1">
<div className="text-white font-semibold">Referral li bendê</div>
<div className="text-white font-semibold">
{t('rewards.pendingReferral')}
</div>
<div className="text-sm text-blue-300">
KYC temam bike ji bo pejirandina referral ji{' '}
{t('rewards.completeKyc')}{' '}
<span className="font-mono">
{formatAddress(stats.pendingReferral, 6)}
</span>
@@ -338,11 +337,11 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<h3 className="font-medium text-foreground mb-3 flex items-center gap-2">
<Share2 className="w-4 h-4 text-primary" />
Hevalên xwe vexwîne
{t('rewards.inviteFriends')}
</h3>
<div className="bg-background rounded-lg p-3 mb-3">
<p className="text-xs text-muted-foreground mb-1">Lînka te</p>
<p className="text-xs text-muted-foreground mb-1">{t('rewards.yourLink')}</p>
<code className="text-sm text-foreground break-all">{referralLink}</code>
</div>
@@ -357,14 +356,14 @@ export function RewardsSection() {
)}
>
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
{copied ? 'Kopî bû!' : 'Kopî bike'}
{copied ? t('rewards.copySuccess') : t('rewards.copyLink')}
</button>
<button
onClick={handleShare}
className="flex-1 flex items-center justify-center gap-2 py-2.5 bg-primary rounded-lg text-primary-foreground text-sm font-medium"
>
<Share2 className="w-4 h-4" />
Parve bike
{t('rewards.shareLink')}
</button>
</div>
</div>
@@ -373,7 +372,7 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<h3 className="font-medium text-foreground mb-3 flex items-center gap-2">
<Star className="w-4 h-4 text-yellow-400" />
Sîstema pûanan
{t('rewards.scoreSystem')}
</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between py-2 border-b border-border/30">
@@ -403,9 +402,11 @@ export function RewardsSection() {
className={cn('w-5 h-5', isActive ? 'text-green-400' : 'text-gray-400')}
/>
<div>
<h3 className="font-medium text-foreground">Rewşa Aktîvbûnê</h3>
<h3 className="font-medium text-foreground">{t('rewards.activeStatus')}</h3>
{isActive && timeRemaining && (
<p className="text-xs text-green-400">Dem: {timeRemaining} maye</p>
<p className="text-xs text-green-400">
{t('rewards.timeRemaining', { time: timeRemaining })}
</p>
)}
</div>
</div>
@@ -415,11 +416,11 @@ export function RewardsSection() {
isActive ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
)}
>
{isActive ? 'Aktîv' : 'Ne Aktîv'}
{isActive ? t('rewards.active') : t('rewards.inactive')}
</div>
</div>
<p className="text-sm text-muted-foreground mb-3">
Her 24 saet carekê bikirtîne da ku aktîv bimînî û xelatên zêdetir qezenc bikî!
{t('rewards.activeDescription')}
</p>
<button
onClick={handleActivate}
@@ -432,7 +433,7 @@ export function RewardsSection() {
)}
>
<Zap className="w-5 h-5" />
{isActive ? 'Tu Aktîv î!' : 'Ez Aktîv im!'}
{isActive ? t('rewards.youAreActive') : t('rewards.iAmActive')}
</button>
</div>
@@ -454,11 +455,9 @@ export function RewardsSection() {
</div>
<div>
<h4 className="font-semibold text-amber-100 mb-1">
زیاتر ڕیفەر بکە، زیاتر قازانج بکە!
{t('rewards.referMoreTitle')}
</h4>
<p className="text-sm text-amber-200/80">
هەر کەسێک بهێنیت، HEZ و PEZ وەک خەڵات وەردەگریت. زیاتر ڕیفەر = زیاتر خەڵات!
</p>
<p className="text-sm text-amber-200/80">{t('rewards.referMoreDescription')}</p>
</div>
</div>
</div>
@@ -469,9 +468,11 @@ export function RewardsSection() {
<div className="flex items-center gap-2">
<Zap className={cn('w-5 h-5', isActive ? 'text-green-400' : 'text-gray-400')} />
<div>
<h3 className="font-medium text-foreground">Rewşa Aktîvbûnê</h3>
<h3 className="font-medium text-foreground">{t('rewards.activeStatus')}</h3>
{isActive && timeRemaining && (
<p className="text-xs text-green-400">Dem: {timeRemaining} maye</p>
<p className="text-xs text-green-400">
{t('rewards.timeRemaining', { time: timeRemaining })}
</p>
)}
</div>
</div>
@@ -481,7 +482,7 @@ export function RewardsSection() {
isActive ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
)}
>
{isActive ? 'Aktîv' : 'Ne Aktîv'}
{isActive ? t('rewards.active') : t('rewards.inactive')}
</div>
</div>
<button
@@ -495,7 +496,7 @@ export function RewardsSection() {
)}
>
<Zap className="w-5 h-5" />
{isActive ? 'Tu Aktîv î!' : 'Ez Aktîv im!'}
{isActive ? t('rewards.youAreActive') : t('rewards.iAmActive')}
</button>
</div>
@@ -510,7 +511,7 @@ export function RewardsSection() {
) : myReferrals.length > 0 ? (
<div className="space-y-3">
<div className="text-sm text-muted-foreground mb-2">
{myReferrals.length} referral (KYC pejirandî)
{t('rewards.referralCount', { count: myReferrals.length })}
</div>
{myReferrals.map((referralAddress, index) => (
<div
@@ -524,11 +525,11 @@ export function RewardsSection() {
<code className="text-sm text-foreground">
{formatAddress(referralAddress, 8)}
</code>
<p className="text-xs text-green-400">KYC Pejirandî</p>
<p className="text-xs text-green-400">{t('rewards.kycApproved')}</p>
</div>
<div className="text-right">
<span className="text-green-400 text-sm font-medium">
+{getPointsForPosition(index + 1)} pûan
+{getPointsForPosition(index + 1)} {t('rewards.points')}
</span>
</div>
</div>
@@ -537,14 +538,14 @@ export function RewardsSection() {
) : (
<div className="flex flex-col items-center justify-center py-12 text-center">
<Users className="w-12 h-12 text-muted-foreground mb-4" />
<p className="text-muted-foreground">Hêj referralên te tune ne</p>
<p className="text-sm text-muted-foreground mt-1">Lînka xwe parve bike!</p>
<p className="text-muted-foreground">{t('rewards.noReferrals')}</p>
<p className="text-sm text-muted-foreground mt-1">{t('rewards.shareYourLink')}</p>
<button
onClick={handleShare}
className="mt-4 flex items-center gap-2 px-4 py-2 bg-primary rounded-lg text-primary-foreground text-sm font-medium"
>
<Share2 className="w-4 h-4" />
Parve bike
{t('rewards.shareLink')}
</button>
</div>
)}
@@ -571,7 +572,7 @@ export function RewardsSection() {
<div>
<p className="text-purple-100 text-sm flex items-center gap-2">
<Shield className="w-4 h-4" />
Pûana Pêbaweriyê (Trust)
{t('rewards.trustScore')}
</p>
<p
className={cn(
@@ -588,11 +589,11 @@ export function RewardsSection() {
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-purple-100">
Rêze: {getScoreRating(userScores?.trustScore || 0)}
{t('rewards.rank', { rating: getScoreRating(userScores?.trustScore || 0) })}
</span>
{userScores?.isCitizen && (
<span className="bg-green-500/30 text-green-200 px-2 py-1 rounded-full text-xs">
Welatî
{t('rewards.citizen')}
</span>
)}
</div>
@@ -604,7 +605,7 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Target className="w-4 h-4 text-blue-400" />
<span className="text-xs text-muted-foreground">Staking</span>
<span className="text-xs text-muted-foreground">{t('rewards.staking')}</span>
</div>
<p className="text-2xl font-bold text-blue-400">
{stakingStatus?.isTracking ? (
@@ -613,23 +614,27 @@ export function RewardsSection() {
{formatDuration(stakingStatus.durationBlocks)}
</span>
) : (
<span className="text-sm text-muted-foreground">Nehatiye destpêkirin</span>
<span className="text-sm text-muted-foreground">
{t('rewards.stakingNotStarted')}
</span>
)}
</p>
<p className="text-xs text-muted-foreground mt-1">Di Trust de hesibandin</p>
<p className="text-xs text-muted-foreground mt-1">
{t('rewards.stakingCountedInTrust')}
</p>
</div>
{/* Referral Score */}
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Users className="w-4 h-4 text-green-400" />
<span className="text-xs text-muted-foreground">Referral</span>
<span className="text-xs text-muted-foreground">{t('rewards.referral')}</span>
</div>
<p className="text-2xl font-bold text-green-400">
{userScores?.referralScore ?? 0}
</p>
<p className="text-xs text-muted-foreground mt-1">
{stats?.referralCount ?? 0} kes
{t('rewards.people', { count: stats?.referralCount ?? 0 })}
</p>
</div>
@@ -637,24 +642,26 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<Sparkles className="w-4 h-4 text-yellow-400" />
<span className="text-xs text-muted-foreground">Tiki</span>
<span className="text-xs text-muted-foreground">{t('rewards.tiki')}</span>
</div>
<p className="text-2xl font-bold text-yellow-400">
{userScores?.tikiScore ?? 0}
</p>
<p className="text-xs text-muted-foreground mt-1">Rola NFT</p>
<p className="text-xs text-muted-foreground mt-1">{t('rewards.nftRole')}</p>
</div>
{/* Perwerde Score */}
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<div className="flex items-center gap-2 mb-2">
<GraduationCap className="w-4 h-4 text-pink-400" />
<span className="text-xs text-muted-foreground">Perwerde</span>
<span className="text-xs text-muted-foreground">
{t('rewards.education')}
</span>
</div>
<p className="text-2xl font-bold text-pink-400">
{userScores?.perwerdeScore ?? 0}
</p>
<p className="text-xs text-muted-foreground mt-1">Xwendin</p>
<p className="text-xs text-muted-foreground mt-1">{t('rewards.reading')}</p>
</div>
</div>
@@ -662,12 +669,12 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<h3 className="font-medium text-foreground mb-3 flex items-center gap-2">
<Coins className="w-4 h-4 text-amber-400" />
Xelatên Staking
{t('rewards.stakingRewards')}
</h3>
{/* Total Accumulated */}
<div className="bg-amber-500/10 rounded-lg p-3 mb-3 border border-amber-500/20">
<p className="text-xs text-amber-300 mb-1">Tevahiya xelatên wergirtî</p>
<p className="text-xs text-amber-300 mb-1">{t('rewards.totalRewards')}</p>
<p className="text-2xl font-bold text-amber-400">
{stakingRewards && stakingRewards.totalAccumulatedHez > 0
? `${stakingRewards.totalAccumulatedHez.toFixed(4)} HEZ`
@@ -678,7 +685,9 @@ export function RewardsSection() {
{/* Recent Rewards List */}
{stakingRewards && stakingRewards.rewards.length > 0 ? (
<div className="space-y-2">
<p className="text-xs text-muted-foreground mb-2">Xelatên dawî</p>
<p className="text-xs text-muted-foreground mb-2">
{t('rewards.recentRewards')}
</p>
{stakingRewards.rewards.map((reward) => (
<div
key={reward.id}
@@ -709,7 +718,7 @@ export function RewardsSection() {
</div>
) : (
<p className="text-sm text-muted-foreground text-center py-3">
Hêj xelatek nehatiye tomarkirin
{t('rewards.noRewardsYet')}
</p>
)}
</div>
@@ -718,7 +727,7 @@ export function RewardsSection() {
<div className="bg-secondary/30 rounded-xl p-4 border border-border/50">
<h3 className="font-medium text-foreground mb-3 flex items-center gap-2">
<Star className="w-4 h-4 text-yellow-400" />
Formûla Pûanê
{t('rewards.scoreFormula')}
</h3>
<div className="text-sm text-muted-foreground space-y-2">
<p>Trust = (Staking × Weighted Sum) / 100</p>
@@ -726,9 +735,7 @@ export function RewardsSection() {
Weighted Sum = Staking×100 + Referral×300 + Perwerde×300 + Tiki×300
</p>
<div className="mt-3 p-2 bg-yellow-500/10 rounded-lg border border-yellow-500/20">
<p className="text-yellow-300 text-xs">
Staking 0 be, Trust pûan 0 dibe. Berî her tiştî stake bike!
</p>
<p className="text-yellow-300 text-xs">{t('rewards.stakingZeroWarning')}</p>
</div>
</div>
</div>
@@ -740,7 +747,7 @@ export function RewardsSection() {
className="w-full py-3 bg-primary rounded-lg text-primary-foreground font-medium flex items-center justify-center gap-2"
>
<RefreshCw className={cn('w-4 h-4', scoresLoading && 'animate-spin')} />
Pûanan Nûve Bike
{t('rewards.refreshScores')}
</button>
</>
)}
+7 -6
View File
@@ -16,6 +16,7 @@ import {
} from '@/components/wallet';
import { LoadingScreen } from '@/components/LoadingScreen';
import { VersionInfo } from '@/components/VersionInfo';
import { useTranslation } from '@/i18n';
type Screen = 'loading' | 'auth-error' | 'setup' | 'create' | 'import' | 'connect' | 'dashboard';
type UserScreen = 'create' | 'import' | null;
@@ -23,6 +24,7 @@ type UserScreen = 'create' | 'import' | null;
export function WalletSection() {
const { isInitialized, isConnected, hasWallet, deleteWalletData } = useWallet();
const { isAuthenticated, isLoading: authLoading, authError, signIn } = useAuth();
const { t } = useTranslation();
const [userScreen, setUserScreen] = useState<UserScreen>(null);
// Derive screen from wallet state and user navigation
@@ -80,10 +82,8 @@ export function WalletSection() {
<AlertTriangle className="w-8 h-8 text-red-400" />
</div>
<div>
<h2 className="text-xl font-semibold mb-2">Teketin Tek Cu</h2>
<p className="text-muted-foreground text-sm">
Ji kerema xwe pistrast bikin ku hun ve app-e di nav Telegram de vedikin
</p>
<h2 className="text-xl font-semibold mb-2">{t('wallet.authFailed')}</h2>
<p className="text-muted-foreground text-sm">{t('wallet.authDescription')}</p>
{authError && (
<p className="text-xs text-red-400 mt-2 font-mono break-all px-4">{authError}</p>
)}
@@ -93,7 +93,7 @@ export function WalletSection() {
className="px-6 py-3 bg-primary text-primary-foreground rounded-xl font-semibold flex items-center gap-2 mx-auto"
>
<RefreshCw className="w-4 h-4" />
Disa Biceribine
{t('wallet.retry')}
</button>
</div>
</div>
@@ -132,6 +132,7 @@ export function WalletSection() {
// Header component
function Header() {
const { t } = useTranslation();
return (
<header className="flex-shrink-0 px-4 py-3 border-b border-border bg-background/80 backdrop-blur-sm safe-area-top">
<div className="flex items-center justify-between">
@@ -139,7 +140,7 @@ function Header() {
<div className="w-8 h-8 rounded-lg bg-cyan-500/20 flex items-center justify-center">
<Wallet className="w-4 h-4 text-cyan-400" />
</div>
<h1 className="text-lg font-semibold">Berîk</h1>
<h1 className="text-lg font-semibold">{t('wallet.title')}</h1>
</div>
<VersionInfo />
</div>
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "1.0.185",
"buildTime": "2026-02-14T08:02:05.985Z",
"buildNumber": 1771056125986
"version": "1.0.186",
"buildTime": "2026-02-14T08:06:14.928Z",
"buildNumber": 1771056374928
}