feat: Multi-language support (6 languages), Smart WebSocket fallback, Complete translations for nav/footer

This commit is contained in:
2025-10-28 09:10:24 +03:00
parent 9bf1c79e44
commit e5a15e29b9
8 changed files with 274 additions and 79 deletions
+65 -20
View File
@@ -31,19 +31,54 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
const reconnectTimeout = useRef<NodeJS.Timeout>();
const eventListeners = useRef<Map<string, Set<(data: any) => void>>>(new Map());
const { toast } = useToast();
// Connection state management
const currentEndpoint = useRef<string>('');
const hasShownFinalError = useRef(false);
const connectionAttempts = useRef(0);
const ENDPOINTS = [
'wss://ws.pezkuwichain.io', // Production WebSocket
'ws://localhost:9944', // Local development node
'ws://127.0.0.1:9944', // Alternative local address
];
const connect = useCallback((endpointIndex: number = 0) => {
// If we've tried all endpoints, show error once and stop
if (endpointIndex >= ENDPOINTS.length) {
if (!hasShownFinalError.current) {
console.error('❌ All WebSocket endpoints failed');
toast({
title: "Real-time Connection Unavailable",
description: "Could not connect to WebSocket server. Live updates will be disabled.",
variant: "destructive",
});
hasShownFinalError.current = true;
}
return;
}
const connect = useCallback(() => {
try {
// In production, replace with actual WebSocket server URL
const wsUrl = import.meta.env.VITE_WS_URL || 'wss://pezkuwichain-ws.example.com';
const wsUrl = ENDPOINTS[endpointIndex];
currentEndpoint.current = wsUrl;
console.log(`🔌 Attempting WebSocket connection to: ${wsUrl}`);
ws.current = new WebSocket(wsUrl);
ws.current.onopen = () => {
setIsConnected(true);
toast({
title: "Connected",
description: "Real-time updates enabled",
});
connectionAttempts.current = 0;
hasShownFinalError.current = false;
console.log(`✅ WebSocket connected to: ${wsUrl}`);
// Only show success toast for production endpoint
if (endpointIndex === 0) {
toast({
title: "Connected",
description: "Real-time updates enabled",
});
}
};
ws.current.onmessage = (event) => {
@@ -59,28 +94,36 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
};
ws.current.onerror = (error) => {
console.error('WebSocket error:', error);
toast({
title: "Connection Error",
description: "Failed to establish real-time connection",
variant: "destructive",
});
console.warn(`⚠️ WebSocket error on ${wsUrl}:`, error);
};
ws.current.onclose = () => {
setIsConnected(false);
// Attempt to reconnect after 5 seconds
console.log(`🔌 WebSocket disconnected from: ${wsUrl}`);
// Try next endpoint after 2 seconds
reconnectTimeout.current = setTimeout(() => {
connect();
}, 5000);
connectionAttempts.current++;
// If we've been connected before and lost connection, try same endpoint first
if (connectionAttempts.current < 3) {
connect(endpointIndex);
} else {
// Try next endpoint in the list
connect(endpointIndex + 1);
connectionAttempts.current = 0;
}
}, 2000);
};
} catch (error) {
console.error('Failed to create WebSocket connection:', error);
console.error(`Failed to create WebSocket connection to ${ENDPOINTS[endpointIndex]}:`, error);
// Try next endpoint immediately
setTimeout(() => connect(endpointIndex + 1), 1000);
}
}, [toast]);
useEffect(() => {
connect();
connect(0); // Start with first endpoint
return () => {
if (reconnectTimeout.current) {
@@ -107,7 +150,7 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
if (ws.current?.readyState === WebSocket.OPEN) {
ws.current.send(JSON.stringify(message));
} else {
console.warn('WebSocket is not connected');
console.warn('WebSocket is not connected - message queued');
}
}, []);
@@ -115,7 +158,9 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
if (ws.current) {
ws.current.close();
}
connect();
hasShownFinalError.current = false;
connectionAttempts.current = 0;
connect(0); // Start from first endpoint again
}, [connect]);
return (
+18 -3
View File
@@ -1,16 +1,31 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { enTranslations, trTranslations } from './translations';
// Import all language translations
import enTranslations from './locales/en';
import trTranslations from './locales/tr';
import kmrTranslations from './locales/kmr';
import ckbTranslations from './locales/ckb';
import arTranslations from './locales/ar';
import faTranslations from './locales/fa';
export const languages = {
en: { name: 'English', flag: '🇬🇧', dir: 'ltr' },
tr: { name: 'Türkçe', flag: '🇹🇷', dir: 'ltr' }
tr: { name: 'Türkçe', flag: '🇹🇷', dir: 'ltr' },
kmr: { name: 'Kurdî (Kurmancî)', flag: '☀️', dir: 'ltr' },
ckb: { name: 'کوردی (سۆرانی)', flag: '☀️', dir: 'rtl' },
ar: { name: 'العربية', flag: '🇸🇦', dir: 'rtl' },
fa: { name: 'فارسی', flag: '🇮🇷', dir: 'rtl' }
};
const resources = {
en: { translation: enTranslations },
tr: { translation: trTranslations }
tr: { translation: trTranslations },
kmr: { translation: kmrTranslations },
ckb: { translation: ckbTranslations },
ar: { translation: arTranslations },
fa: { translation: faTranslations }
};
i18n
+33 -6
View File
@@ -2,18 +2,26 @@ export default {
// Navigation
'nav.home': 'الرئيسية',
'nav.dashboard': 'لوحة التحكم',
'nav.wallet': 'المحفظة',
'nav.settings': 'الإعدادات',
'nav.governance': 'الحوكمة',
'nav.proposals': 'المقترحات',
'nav.delegation': 'التفويض',
'nav.treasury': 'الخزينة',
'nav.staking': 'التخزين',
'nav.forum': 'المنتدى',
'nav.moderation': 'الإشراف',
'nav.profile': 'الملف الشخصي',
'nav.admin': 'المدير',
'nav.admin': 'المسؤول',
'nav.docs': 'التوثيق',
'nav.login': 'تسجيل الدخول',
'nav.logout': 'تسجيل الخروج',
// Hero Section
'hero.title': 'منصة حوكمة البلوكشين',
'hero.title': 'منصة الحوكمة بتقنية البلوكشين',
'hero.subtitle': 'حوكمة ديمقراطية وشفافة بتقنية البلوكشين',
'hero.cta.primary': 'ابدأ الآن',
'hero.cta.secondary': 'اعرف المزيد',
'hero.cta.secondary': 'معرفة المزيد',
// Auth
'auth.login': 'تسجيل الدخول',
@@ -23,7 +31,7 @@ export default {
'auth.password': 'كلمة المرور',
'auth.confirmPassword': 'تأكيد كلمة المرور',
'auth.rememberMe': 'تذكرني',
'auth.forgotPassword': 'نسيت كلمة المرور؟',
'auth.forgotPassword': 'هل نسيت كلمة المرور؟',
// Wallet
'wallet.connect': 'ربط المحفظة',
@@ -36,10 +44,25 @@ export default {
'governance.proposals': 'المقترحات',
'governance.activeProposals': 'المقترحات النشطة',
'governance.vote': 'التصويت',
'governance.delegate': 'التفويض',
'governance.delegate': 'المندوب',
'governance.createProposal': 'إنشاء مقترح',
'governance.votingPower': 'قوة التصويت',
// Treasury
'treasury.title': 'الخزينة',
'treasury.subtitle': 'إدارة أموال المجتمع',
'treasury.overview': 'نظرة عامة',
'treasury.proposals': 'المقترحات',
'treasury.history': 'السجل',
'treasury.approvals': 'الموافقات',
// Footer
'footer.description': 'حوكمة لامركزية لكردستان',
'footer.about': 'حول',
'footer.developers': 'المطورون',
'footer.community': 'المجتمع',
'footer.rights': 'جميع الحقوق محفوظة',
// Common
'common.loading': 'جاري التحميل...',
'common.save': 'حفظ',
@@ -55,5 +78,9 @@ export default {
'common.next': 'التالي',
'common.previous': 'السابق',
'common.yes': 'نعم',
'common.no': 'لا'
'common.no': 'لا',
'common.backToHome': 'العودة للرئيسية',
// Role
'role': 'الدور'
}
+43 -16
View File
@@ -2,32 +2,40 @@ export default {
// Navigation
'nav.home': 'سەرەتا',
'nav.dashboard': 'داشبۆرد',
'nav.wallet': 'جزدان',
'nav.settings': 'ڕێکخستنەکان',
'nav.governance': 'حوکمڕانی',
'nav.treasury': 'خەزێنە',
'nav.proposals': 'پێشنیارەکان',
'nav.delegation': 'دیلیگەیشن',
'nav.treasury': 'گەنجینە',
'nav.staking': 'ستەیکینگ',
'nav.forum': 'فۆرەم',
'nav.forum': 'فۆرۆم',
'nav.moderation': 'بەڕێوەبردنی ناوەڕۆک',
'nav.profile': 'پرۆفایل',
'nav.admin': 'بەڕێوەبەر',
'nav.docs': 'بەڵگەنامە',
'nav.login': 'چوونە ژوورەوە',
'nav.logout': 'چوونە دەرەوە',
// Hero Section
'hero.title': 'پلاتفۆرمی حوکمڕانی بلۆکچەین',
'hero.subtitle': 'حوکمڕانی دیموکراتی و شەفاف بە تەکنەلۆژیای بلۆکچەین',
'hero.cta.primary': 'دەست پێ بکە',
'hero.cta.secondary': 'زیاتر بزانە',
'hero.subtitle': 'حوکمڕانی دیموکراتیک و شەفاف بە تەکنەلۆژیای بلۆکچەین',
'hero.cta.primary': 'دەستپێکردن',
'hero.cta.secondary': انیاری زیاتر',
// Auth
'auth.login': 'چوونەژوورەوە',
'auth.logout': 'دەرچوون',
'auth.login': 'چوونە ژوورەوە',
'auth.logout': 'چوونە دەرەوە',
'auth.signup': 'تۆمارکردن',
'auth.email': 'ئیمەیڵ',
'auth.password': 'وشەی نهێنی',
'auth.confirmPassword': 'دووبارەکردنەوەی وشەی نهێنی',
'auth.rememberMe': 'بمهێنەوە یاد',
'auth.forgotPassword': 'وشەی نهێنیت لەبیر چووە؟',
'auth.rememberMe': 'بمهێڵەوە لە بیرت',
'auth.forgotPassword': 'وشەی نهێنیم لەبیر چووە؟',
// Wallet
'wallet.connect': 'جزدان بەستنەوە',
'wallet.disconnect': 'پچڕاندن',
'wallet.connect': 'بەستنەوەی جزدان',
'wallet.disconnect': 'لابردنی بەستنەوە',
'wallet.balance': 'باڵانس',
'wallet.address': 'ناونیشان',
'wallet.network': 'تۆڕ',
@@ -37,23 +45,42 @@ export default {
'governance.activeProposals': 'پێشنیارە چالاکەکان',
'governance.vote': 'دەنگدان',
'governance.delegate': 'نوێنەر',
'governance.createProposal': 'پێشنیار دروست بکە',
'governance.createProposal': 'دروستکردنی پێشنیار',
'governance.votingPower': 'هێزی دەنگدان',
// Treasury
'treasury.title': 'گەنجینە',
'treasury.subtitle': 'بەڕێوەبردنی سامانی کۆمەڵگا',
'treasury.overview': 'تێڕوانینی گشتی',
'treasury.proposals': 'پێشنیارەکان',
'treasury.history': 'مێژوو',
'treasury.approvals': 'پەسەندکراوەکان',
// Footer
'footer.description': 'حوکمڕانی لامەرکەزی بۆ کوردستان',
'footer.about': 'دەربارە',
'footer.developers': 'گەشەپێدەران',
'footer.community': 'کۆمەڵگا',
'footer.rights': 'هەموو مافێک پارێزراوە',
// Common
'common.loading': 'چاوەڕوان بە...',
'common.loading': 'بارکردن...',
'common.save': 'پاشەکەوتکردن',
'common.cancel': 'هەڵوەشاندنەوە',
'common.confirm': 'دڵنیاکردنەوە',
'common.confirm': 'پشتڕاستکردنەوە',
'common.delete': 'سڕینەوە',
'common.edit': 'دەستکاریکردن',
'common.search': 'گەڕان',
'common.filter': 'فلتەر',
'common.filter': 'پاڵاوتن',
'common.sort': 'ڕیزکردن',
'common.submit': 'ناردن',
'common.back': 'گەڕانەوە',
'common.next': 'دواتر',
'common.previous': 'پێشوو',
'common.yes': 'بەڵێ',
'common.no': 'نەخێر'
'common.no': 'نەخێر',
'common.backToHome': 'گەڕانەوە بۆ سەرەتا',
// Role
'role': 'ڕۆڵ'
}
+36 -9
View File
@@ -1,19 +1,27 @@
export default {
// Navigation
'nav.home': 'خانه',
'nav.home': 'صفحه اصلی',
'nav.dashboard': 'داشبورد',
'nav.wallet': 'کیف پول',
'nav.settings': 'تنظیمات',
'nav.governance': 'حکمرانی',
'nav.proposals': 'پیشنهادات',
'nav.delegation': 'نمایندگی',
'nav.treasury': 'خزانه',
'nav.staking': 'استیکینگ',
'nav.forum': 'انجمن',
'nav.moderation': 'مدیریت محتوا',
'nav.profile': 'پروفایل',
'nav.admin': 'مدیر',
'nav.admin': 'مدیریت',
'nav.docs': 'مستندات',
'nav.login': 'ورود',
'nav.logout': 'خروج',
// Hero Section
'hero.title': 'پلتفرم حکمرانی بلاکچین',
'hero.subtitle': 'حکمرانی دموکراتیک و شفاف با فناوری بلاکچین',
'hero.subtitle': 'حکمرانی دموکراتیک و شفاف با تکنولوژی بلاکچین',
'hero.cta.primary': 'شروع کنید',
'hero.cta.secondary': 'بیشتر بدانید',
'hero.cta.secondary': 'اطلاعات بیشتر',
// Auth
'auth.login': 'ورود',
@@ -21,7 +29,7 @@ export default {
'auth.signup': 'ثبت نام',
'auth.email': 'ایمیل',
'auth.password': 'رمز عبور',
'auth.confirmPassword': ایید رمز عبور',
'auth.confirmPassword': کرار رمز عبور',
'auth.rememberMe': 'مرا به خاطر بسپار',
'auth.forgotPassword': 'رمز عبور را فراموش کرده‌اید؟',
@@ -35,16 +43,31 @@ export default {
// Governance
'governance.proposals': 'پیشنهادات',
'governance.activeProposals': 'پیشنهادات فعال',
'governance.vote': ای دادن',
'governance.vote': أی دادن',
'governance.delegate': 'نماینده',
'governance.createProposal': 'ایجاد پیشنهاد',
'governance.votingPower': 'قدرت رای',
'governance.votingPower': 'قدرت رأی',
// Treasury
'treasury.title': 'خزانه',
'treasury.subtitle': 'مدیریت منابع جامعه',
'treasury.overview': 'نمای کلی',
'treasury.proposals': 'پیشنهادات',
'treasury.history': 'تاریخچه',
'treasury.approvals': 'تأییدیه‌ها',
// Footer
'footer.description': 'حکمرانی غیرمتمرکز برای کردستان',
'footer.about': 'درباره',
'footer.developers': 'توسعه‌دهندگان',
'footer.community': 'جامعه',
'footer.rights': 'تمامی حقوق محفوظ است',
// Common
'common.loading': 'در حال بارگذاری...',
'common.save': 'ذخیره',
'common.cancel': 'لغو',
'common.confirm': ایید',
'common.confirm': أیید',
'common.delete': 'حذف',
'common.edit': 'ویرایش',
'common.search': 'جستجو',
@@ -55,5 +78,9 @@ export default {
'common.next': 'بعدی',
'common.previous': 'قبلی',
'common.yes': 'بله',
'common.no': 'خیر'
'common.no': 'خیر',
'common.backToHome': 'بازگشت به صفحه اصلی',
// Role
'role': 'نقش'
}
+50 -23
View File
@@ -1,33 +1,41 @@
export default {
// Navigation
'nav.home': 'Destpêk',
'nav.home': 'Malper',
'nav.dashboard': 'Panela Kontrolê',
'nav.wallet': 'Berîk',
'nav.settings': 'Mîhengên',
'nav.governance': 'Rêveberî',
'nav.proposals': 'Pêşniyar',
'nav.delegation': 'Delegasyon',
'nav.treasury': 'Xezîne',
'nav.staking': 'Staking',
'nav.forum': 'Forum',
'nav.moderation': 'Rêveberiya Naverokê',
'nav.profile': 'Profîl',
'nav.admin': 'Rêvebir',
'nav.docs': 'Belgekirin',
'nav.login': 'Têketin',
'nav.logout': 'Derkevtin',
// Hero Section
'hero.title': 'Platforma Rêveberiya Blockchain',
'hero.subtitle': 'Rêveberiya demokratîk û şeffaf a bi teknolojiya blockchain',
'hero.cta.primary': 'Dest Pê Bike',
'hero.cta.secondary': 'Zêdetir Bizane',
'hero.subtitle': 'Rêveberiya demokratîk û zelal bi teknolojiya blockchain',
'hero.cta.primary': 'Destpêkirin',
'hero.cta.secondary': 'Zêdetir Zanîn',
// Auth
'auth.login': 'Têkeve',
'auth.logout': 'Derkeve',
'auth.signup': 'Tomar Bibe',
'auth.email': 'E-peyam',
'auth.login': 'Têketin',
'auth.logout': 'Derkevtin',
'auth.signup': 'Tomar bibe',
'auth.email': 'E-posta',
'auth.password': 'Şîfre',
'auth.confirmPassword': 'Şîfreyê Piştrast Bike',
'auth.confirmPassword': 'Şîfreya Dubare',
'auth.rememberMe': 'Min bi bîr bîne',
'auth.forgotPassword': 'Şîfreya min ji bîr kir?',
'auth.forgotPassword': 'Şîfreya xwe ji bîr kir?',
// Wallet
'wallet.connect': 'Wallet Girê Bide',
'wallet.disconnect': 'Veqetîne',
'wallet.connect': 'Berîkê Girêbide',
'wallet.disconnect': 'Girêdanê Rake',
'wallet.balance': 'Balans',
'wallet.address': 'Navnîşan',
'wallet.network': 'Tor',
@@ -35,25 +43,44 @@ export default {
// Governance
'governance.proposals': 'Pêşniyar',
'governance.activeProposals': 'Pêşniyarên Çalak',
'governance.vote': 'Deng Bide',
'governance.delegate': 'Temsîlkar',
'governance.createProposal': 'Pêşniyar Biafirîne',
'governance.vote': 'Deng Bidin',
'governance.delegate': 'Nûner',
'governance.createProposal': 'Pêşniyarek Çêbikin',
'governance.votingPower': 'Hêza Dengdanê',
// Treasury
'treasury.title': 'Xezîne',
'treasury.subtitle': 'Fonên civakê rêve bibin',
'treasury.overview': 'Gihştin Giştî',
'treasury.proposals': 'Pêşniyar',
'treasury.history': 'Dîrok',
'treasury.approvals': 'Pejirandin',
// Footer
'footer.description': 'Rêveberiya merkezî tune ji bo Kurdistan',
'footer.about': 'Derbarê',
'footer.developers': 'Pêşvebikar',
'footer.community': 'Civak',
'footer.rights': 'Hemû mafên parastin',
// Common
'common.loading': 'Tê barkirin...',
'common.save': 'Tomar Bike',
'common.cancel': 'Betal',
'common.confirm': 'Piştrast Bike',
'common.delete': 'Jê Bibe',
'common.edit': 'Biguherîne',
'common.save': 'Tomar bike',
'common.cancel': 'Betal bike',
'common.confirm': 'Piştrast bike',
'common.delete': 'Jê bibe',
'common.edit': 'Sererast bike',
'common.search': 'Lêgerîn',
'common.filter': 'Parzûn',
'common.sort': 'Rêz Bike',
'common.filter': 'Parzûn bike',
'common.sort': 'Rêz bike',
'common.submit': 'Bişîne',
'common.back': 'Paşve',
'common.next': 'Pêşve',
'common.previous': 'Berê',
'common.yes': 'Erê',
'common.no': 'Na'
'common.no': 'Na',
'common.backToHome': 'Vegere Malperê',
// Role
'role': 'Rol'
}
+29 -2
View File
@@ -2,12 +2,20 @@ export default {
// Navigation
'nav.home': 'Ana Sayfa',
'nav.dashboard': 'Kontrol Paneli',
'nav.wallet': 'Cüzdan',
'nav.settings': 'Ayarlar',
'nav.governance': 'Yönetişim',
'nav.proposals': 'Teklifler',
'nav.delegation': 'Delegasyon',
'nav.treasury': 'Hazine',
'nav.staking': 'Staking',
'nav.staking': 'Stake Etme',
'nav.forum': 'Forum',
'nav.moderation': 'Moderasyon',
'nav.profile': 'Profil',
'nav.admin': 'Yönetici',
'nav.docs': 'Dokümantasyon',
'nav.login': 'Giriş Yap',
'nav.logout': 'Çıkış Yap',
// Hero Section
'hero.title': 'Blockchain Yönetişim Platformu',
@@ -40,6 +48,21 @@ export default {
'governance.createProposal': 'Öneri Oluştur',
'governance.votingPower': 'Oy Gücü',
// Treasury
'treasury.title': 'Hazine',
'treasury.subtitle': 'Topluluk fonlarını yönetin',
'treasury.overview': 'Genel Bakış',
'treasury.proposals': 'Teklifler',
'treasury.history': 'Geçmiş',
'treasury.approvals': 'Onaylar',
// Footer
'footer.description': 'Kürdistan için merkezi olmayan yönetişim',
'footer.about': 'Hakkında',
'footer.developers': 'Geliştiriciler',
'footer.community': 'Topluluk',
'footer.rights': 'Tüm hakları saklıdır',
// Common
'common.loading': 'Yükleniyor...',
'common.save': 'Kaydet',
@@ -55,5 +78,9 @@ export default {
'common.next': 'İleri',
'common.previous': 'Önceki',
'common.yes': 'Evet',
'common.no': 'Hayır'
'common.no': 'Hayır',
'common.backToHome': 'Ana Sayfaya Dön',
// Role
'role': 'Rol'
}