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
+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>