From d121759d2c666ab62b70c77adf157d96ba89d7f9 Mon Sep 17 00:00:00 2001 From: Gulshan Yadav Date: Sat, 10 Jan 2026 06:12:10 +0530 Subject: [PATCH] feat(web-wallet): add multi-language support (i18n) - Add i18next, react-i18next, and browser language detection - Create translations for 6 languages: English, Chinese, Spanish, Korean, Japanese, Russian - Add LanguageSelector component for settings page - Integrate language selection into Settings page with translated security options - Language preference persists to localStorage --- apps/web/package.json | 5 +- apps/web/src/components/LanguageSelector.tsx | 37 +++++ apps/web/src/i18n/index.ts | 45 ++++++ apps/web/src/i18n/locales/en.json | 151 +++++++++++++++++++ apps/web/src/i18n/locales/es.json | 151 +++++++++++++++++++ apps/web/src/i18n/locales/ja.json | 151 +++++++++++++++++++ apps/web/src/i18n/locales/ko.json | 151 +++++++++++++++++++ apps/web/src/i18n/locales/ru.json | 151 +++++++++++++++++++ apps/web/src/i18n/locales/zh.json | 151 +++++++++++++++++++ apps/web/src/main.tsx | 3 + apps/web/src/pages/Settings.tsx | 25 ++- 11 files changed, 1012 insertions(+), 9 deletions(-) create mode 100644 apps/web/src/components/LanguageSelector.tsx create mode 100644 apps/web/src/i18n/index.ts create mode 100644 apps/web/src/i18n/locales/en.json create mode 100644 apps/web/src/i18n/locales/es.json create mode 100644 apps/web/src/i18n/locales/ja.json create mode 100644 apps/web/src/i18n/locales/ko.json create mode 100644 apps/web/src/i18n/locales/ru.json create mode 100644 apps/web/src/i18n/locales/zh.json diff --git a/apps/web/package.json b/apps/web/package.json index 3c29072..452d895 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -21,7 +21,10 @@ "qrcode.react": "^3.1.0", "html5-qrcode": "^2.3.8", "@ledgerhq/hw-transport-webhid": "^6.29.0", - "@trezor/connect-web": "^9.4.0" + "@trezor/connect-web": "^9.4.0", + "i18next": "^23.10.0", + "react-i18next": "^14.0.0", + "i18next-browser-languagedetector": "^7.2.0" }, "devDependencies": { "@types/react": "^18.3.0", diff --git a/apps/web/src/components/LanguageSelector.tsx b/apps/web/src/components/LanguageSelector.tsx new file mode 100644 index 0000000..90c27cb --- /dev/null +++ b/apps/web/src/components/LanguageSelector.tsx @@ -0,0 +1,37 @@ +import { useTranslation } from 'react-i18next'; +import { languages, type LanguageCode } from '../i18n'; + +interface LanguageSelectorProps { + className?: string; +} + +export function LanguageSelector({ className = '' }: LanguageSelectorProps) { + const { i18n, t } = useTranslation(); + + const handleChange = (e: React.ChangeEvent) => { + const newLang = e.target.value as LanguageCode; + i18n.changeLanguage(newLang); + }; + + return ( +
+ + +

+ {t('settings.language.description')} +

+
+ ); +} diff --git a/apps/web/src/i18n/index.ts b/apps/web/src/i18n/index.ts new file mode 100644 index 0000000..66dd1f0 --- /dev/null +++ b/apps/web/src/i18n/index.ts @@ -0,0 +1,45 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +import en from './locales/en.json'; +import zh from './locales/zh.json'; +import es from './locales/es.json'; +import ko from './locales/ko.json'; +import ja from './locales/ja.json'; +import ru from './locales/ru.json'; + +export const languages = { + en: { name: 'English', nativeName: 'English' }, + zh: { name: 'Chinese', nativeName: '中文' }, + es: { name: 'Spanish', nativeName: 'Español' }, + ko: { name: 'Korean', nativeName: '한국어' }, + ja: { name: 'Japanese', nativeName: '日本語' }, + ru: { name: 'Russian', nativeName: 'Русский' }, +} as const; + +export type LanguageCode = keyof typeof languages; + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources: { + en: { translation: en }, + zh: { translation: zh }, + es: { translation: es }, + ko: { translation: ko }, + ja: { translation: ja }, + ru: { translation: ru }, + }, + fallbackLng: 'en', + interpolation: { + escapeValue: false, // React already escapes values + }, + detection: { + order: ['localStorage', 'navigator', 'htmlTag'], + caches: ['localStorage'], + }, + }); + +export default i18n; diff --git a/apps/web/src/i18n/locales/en.json b/apps/web/src/i18n/locales/en.json new file mode 100644 index 0000000..d200216 --- /dev/null +++ b/apps/web/src/i18n/locales/en.json @@ -0,0 +1,151 @@ +{ + "common": { + "appName": "Synor Wallet", + "loading": "Loading...", + "copy": "Copy", + "copied": "Copied!", + "cancel": "Cancel", + "confirm": "Confirm", + "save": "Save", + "close": "Close", + "back": "Back", + "next": "Next", + "done": "Done", + "error": "Error", + "success": "Success", + "warning": "Warning", + "synor": "SYNOR" + }, + "welcome": { + "title": "Welcome to Synor", + "subtitle": "Quantum-secure cryptocurrency wallet", + "createWallet": "Create New Wallet", + "recoverWallet": "Recover Wallet", + "description": "Synor is the first cryptocurrency with post-quantum security using hybrid Ed25519 + Dilithium3 signatures." + }, + "createWallet": { + "title": "Create New Wallet", + "step1Title": "Write Down Recovery Phrase", + "step1Description": "Write down these 24 words in order. This is your wallet backup.", + "step2Title": "Verify Recovery Phrase", + "step2Description": "Select the words in the correct order to verify you saved them.", + "step3Title": "Set Password", + "step3Description": "Create a password to encrypt your wallet on this device.", + "passwordLabel": "Password", + "passwordConfirmLabel": "Confirm Password", + "passwordHint": "At least 8 characters", + "warningTitle": "Important", + "warningText": "Never share your recovery phrase. Anyone with these words can access your funds.", + "createButton": "Create Wallet" + }, + "recoverWallet": { + "title": "Recover Wallet", + "description": "Enter your 24-word recovery phrase to restore your wallet.", + "phraseLabel": "Recovery Phrase", + "phrasePlaceholder": "Enter your 24 words separated by spaces", + "invalidPhrase": "Invalid recovery phrase", + "recoverButton": "Recover Wallet" + }, + "dashboard": { + "title": "Wallet", + "balance": "Balance", + "confirmedBalance": "Confirmed", + "pendingBalance": "Pending", + "send": "Send", + "receive": "Receive", + "history": "History", + "noTransactions": "No transactions yet", + "recentTransactions": "Recent Transactions" + }, + "send": { + "title": "Send SYNOR", + "recipientLabel": "Recipient Address", + "recipientPlaceholder": "synor:qz...", + "amountLabel": "Amount", + "maxButton": "Max", + "networkFee": "Network Fee", + "sendButton": "Send Transaction", + "sending": "Sending...", + "scanQR": "Scan QR Code", + "invalidAddress": "Invalid address format. Must start with \"synor:\"", + "invalidAmount": "Invalid amount", + "insufficientFunds": "Insufficient balance", + "success": { + "title": "Transaction Sent", + "description": "Your transaction has been submitted to the network.", + "txIdLabel": "Transaction ID", + "sendAnother": "Send Another", + "backToWallet": "Back to Wallet" + } + }, + "receive": { + "title": "Receive SYNOR", + "yourAddress": "Your Address", + "requestPayment": "Request Payment", + "amountOptional": "Amount (optional)", + "generateLink": "Copy Payment Link", + "qrUpdated": "QR code updated to request {{amount}} SYNOR", + "warning": "Share this address to receive SYNOR. Only send SYNOR to this address - other cryptocurrencies will be lost." + }, + "history": { + "title": "Transaction History", + "received": "Received", + "sent": "Sent", + "pending": "Pending", + "confirmed": "Confirmed", + "noTransactions": "No transactions yet", + "viewOnExplorer": "View on Explorer" + }, + "settings": { + "title": "Settings", + "network": { + "title": "Network", + "mainnet": "Mainnet", + "mainnetDesc": "Production network", + "testnet": "Testnet", + "testnetDesc": "Test with fake SYNOR", + "devnet": "Devnet", + "devnetDesc": "Local development", + "mainnetWarning": "Mainnet is not yet launched. Switch to testnet or devnet." + }, + "rpcEndpoint": { + "title": "RPC Endpoint", + "description": "Connect to a custom Synor node" + }, + "hardwareWallet": { + "title": "Hardware Wallet", + "description": "Connect a Ledger or Trezor hardware wallet for enhanced security. Your private keys never leave the device.", + "connect": "Connect Hardware Wallet", + "notSupported": "Not Supported in This Browser", + "notSupportedHint": "Use Chrome, Edge, or Brave on desktop for hardware wallet support.", + "connected": "{{type}} Connected", + "disconnect": "Disconnect" + }, + "security": { + "title": "Security", + "autoLock": "Auto-lock", + "autoLockDescription": "Lock wallet after inactivity", + "minutes": "{{count}} minutes", + "hour": "1 hour", + "never": "Never" + }, + "language": { + "title": "Language", + "description": "Select your preferred language" + }, + "dangerZone": { + "title": "Danger Zone", + "deleteWallet": "Delete Wallet", + "deleteWarning": "This will permanently delete your wallet from this device. Make sure you have your recovery phrase backed up.", + "typeToConfirm": "Type \"DELETE\" to confirm", + "confirmDelete": "Confirm Delete" + }, + "version": "Synor Web Wallet v{{version}}" + }, + "errors": { + "walletNotUnlocked": "Wallet not unlocked", + "transactionFailed": "Transaction failed", + "connectionFailed": "Connection failed", + "invalidPassword": "Invalid password" + } +} diff --git a/apps/web/src/i18n/locales/es.json b/apps/web/src/i18n/locales/es.json new file mode 100644 index 0000000..01659e9 --- /dev/null +++ b/apps/web/src/i18n/locales/es.json @@ -0,0 +1,151 @@ +{ + "common": { + "appName": "Synor Wallet", + "loading": "Cargando...", + "copy": "Copiar", + "copied": "Copiado!", + "cancel": "Cancelar", + "confirm": "Confirmar", + "save": "Guardar", + "close": "Cerrar", + "back": "Atrás", + "next": "Siguiente", + "done": "Hecho", + "error": "Error", + "success": "Éxito", + "warning": "Advertencia", + "synor": "SYNOR" + }, + "welcome": { + "title": "Bienvenido a Synor", + "subtitle": "Billetera de criptomonedas con seguridad cuántica", + "createWallet": "Crear Nueva Billetera", + "recoverWallet": "Recuperar Billetera", + "description": "Synor es la primera criptomoneda con seguridad post-cuántica usando firmas híbridas Ed25519 + Dilithium3." + }, + "createWallet": { + "title": "Crear Nueva Billetera", + "step1Title": "Anota la Frase de Recuperación", + "step1Description": "Escribe estas 24 palabras en orden. Este es el respaldo de tu billetera.", + "step2Title": "Verifica la Frase de Recuperación", + "step2Description": "Selecciona las palabras en el orden correcto para verificar que las guardaste.", + "step3Title": "Establece Contraseña", + "step3Description": "Crea una contraseña para encriptar tu billetera en este dispositivo.", + "passwordLabel": "Contraseña", + "passwordConfirmLabel": "Confirmar Contraseña", + "passwordHint": "Al menos 8 caracteres", + "warningTitle": "Importante", + "warningText": "Nunca compartas tu frase de recuperación. Cualquiera con estas palabras puede acceder a tus fondos.", + "createButton": "Crear Billetera" + }, + "recoverWallet": { + "title": "Recuperar Billetera", + "description": "Ingresa tu frase de recuperación de 24 palabras para restaurar tu billetera.", + "phraseLabel": "Frase de Recuperación", + "phrasePlaceholder": "Ingresa tus 24 palabras separadas por espacios", + "invalidPhrase": "Frase de recuperación inválida", + "recoverButton": "Recuperar Billetera" + }, + "dashboard": { + "title": "Billetera", + "balance": "Saldo", + "confirmedBalance": "Confirmado", + "pendingBalance": "Pendiente", + "send": "Enviar", + "receive": "Recibir", + "history": "Historial", + "noTransactions": "Sin transacciones aún", + "recentTransactions": "Transacciones Recientes" + }, + "send": { + "title": "Enviar SYNOR", + "recipientLabel": "Dirección del Destinatario", + "recipientPlaceholder": "synor:qz...", + "amountLabel": "Cantidad", + "maxButton": "Máx", + "networkFee": "Comisión de Red", + "sendButton": "Enviar Transacción", + "sending": "Enviando...", + "scanQR": "Escanear Código QR", + "invalidAddress": "Formato de dirección inválido. Debe comenzar con \"synor:\"", + "invalidAmount": "Cantidad inválida", + "insufficientFunds": "Saldo insuficiente", + "success": { + "title": "Transacción Enviada", + "description": "Tu transacción ha sido enviada a la red.", + "txIdLabel": "ID de Transacción", + "sendAnother": "Enviar Otra", + "backToWallet": "Volver a la Billetera" + } + }, + "receive": { + "title": "Recibir SYNOR", + "yourAddress": "Tu Dirección", + "requestPayment": "Solicitar Pago", + "amountOptional": "Cantidad (opcional)", + "generateLink": "Copiar Enlace de Pago", + "qrUpdated": "Código QR actualizado para solicitar {{amount}} SYNOR", + "warning": "Comparte esta dirección para recibir SYNOR. Solo envía SYNOR a esta dirección - otras criptomonedas se perderán." + }, + "history": { + "title": "Historial de Transacciones", + "received": "Recibido", + "sent": "Enviado", + "pending": "Pendiente", + "confirmed": "Confirmado", + "noTransactions": "Sin transacciones aún", + "viewOnExplorer": "Ver en Explorador" + }, + "settings": { + "title": "Configuración", + "network": { + "title": "Red", + "mainnet": "Red Principal", + "mainnetDesc": "Red de producción", + "testnet": "Red de Prueba", + "testnetDesc": "Prueba con SYNOR falso", + "devnet": "Red de Desarrollo", + "devnetDesc": "Desarrollo local", + "mainnetWarning": "La red principal aún no está activa. Cambia a la red de prueba o desarrollo." + }, + "rpcEndpoint": { + "title": "Endpoint RPC", + "description": "Conectar a un nodo Synor personalizado" + }, + "hardwareWallet": { + "title": "Billetera de Hardware", + "description": "Conecta una billetera de hardware Ledger o Trezor para mayor seguridad. Tus claves privadas nunca salen del dispositivo.", + "connect": "Conectar Billetera de Hardware", + "notSupported": "No Compatible en Este Navegador", + "notSupportedHint": "Usa Chrome, Edge o Brave en escritorio para soporte de billetera de hardware.", + "connected": "{{type}} Conectado", + "disconnect": "Desconectar" + }, + "security": { + "title": "Seguridad", + "autoLock": "Bloqueo Automático", + "autoLockDescription": "Bloquear billetera después de inactividad", + "minutes": "{{count}} minutos", + "hour": "1 hora", + "never": "Nunca" + }, + "language": { + "title": "Idioma", + "description": "Selecciona tu idioma preferido" + }, + "dangerZone": { + "title": "Zona de Peligro", + "deleteWallet": "Eliminar Billetera", + "deleteWarning": "Esto eliminará permanentemente tu billetera de este dispositivo. Asegúrate de tener tu frase de recuperación respaldada.", + "typeToConfirm": "Escribe \"DELETE\" para confirmar", + "confirmDelete": "Confirmar Eliminación" + }, + "version": "Synor Web Wallet v{{version}}" + }, + "errors": { + "walletNotUnlocked": "Billetera no desbloqueada", + "transactionFailed": "Transacción fallida", + "connectionFailed": "Conexión fallida", + "invalidPassword": "Contraseña inválida" + } +} diff --git a/apps/web/src/i18n/locales/ja.json b/apps/web/src/i18n/locales/ja.json new file mode 100644 index 0000000..5132a45 --- /dev/null +++ b/apps/web/src/i18n/locales/ja.json @@ -0,0 +1,151 @@ +{ + "common": { + "appName": "Synor ウォレット", + "loading": "読み込み中...", + "copy": "コピー", + "copied": "コピーしました!", + "cancel": "キャンセル", + "confirm": "確認", + "save": "保存", + "close": "閉じる", + "back": "戻る", + "next": "次へ", + "done": "完了", + "error": "エラー", + "success": "成功", + "warning": "警告", + "synor": "SYNOR" + }, + "welcome": { + "title": "Synorへようこそ", + "subtitle": "量子安全な暗号通貨ウォレット", + "createWallet": "新しいウォレットを作成", + "recoverWallet": "ウォレットを復元", + "description": "Synorは、Ed25519 + Dilithium3ハイブリッド署名を使用した最初のポスト量子セキュリティ暗号通貨です。" + }, + "createWallet": { + "title": "新しいウォレットを作成", + "step1Title": "復元フレーズを記録", + "step1Description": "この24個の単語を順番に書き留めてください。これがウォレットのバックアップです。", + "step2Title": "復元フレーズを確認", + "step2Description": "保存したことを確認するために、正しい順序で単語を選択してください。", + "step3Title": "パスワードを設定", + "step3Description": "このデバイスでウォレットを暗号化するためのパスワードを作成してください。", + "passwordLabel": "パスワード", + "passwordConfirmLabel": "パスワードを確認", + "passwordHint": "8文字以上", + "warningTitle": "重要", + "warningText": "復元フレーズを決して共有しないでください。これらの単語を持っている人は誰でもあなたの資金にアクセスできます。", + "createButton": "ウォレットを作成" + }, + "recoverWallet": { + "title": "ウォレットを復元", + "description": "ウォレットを復元するには、24単語の復元フレーズを入力してください。", + "phraseLabel": "復元フレーズ", + "phrasePlaceholder": "24個の単語をスペースで区切って入力", + "invalidPhrase": "無効な復元フレーズ", + "recoverButton": "ウォレットを復元" + }, + "dashboard": { + "title": "ウォレット", + "balance": "残高", + "confirmedBalance": "確認済み", + "pendingBalance": "保留中", + "send": "送信", + "receive": "受信", + "history": "履歴", + "noTransactions": "取引履歴はありません", + "recentTransactions": "最近の取引" + }, + "send": { + "title": "SYNORを送信", + "recipientLabel": "受信者アドレス", + "recipientPlaceholder": "synor:qz...", + "amountLabel": "金額", + "maxButton": "最大", + "networkFee": "ネットワーク手数料", + "sendButton": "取引を送信", + "sending": "送信中...", + "scanQR": "QRコードをスキャン", + "invalidAddress": "無効なアドレス形式。\"synor:\"で始まる必要があります", + "invalidAmount": "無効な金額", + "insufficientFunds": "残高不足", + "success": { + "title": "取引が送信されました", + "description": "取引がネットワークに送信されました。", + "txIdLabel": "取引ID", + "sendAnother": "別の取引を送信", + "backToWallet": "ウォレットに戻る" + } + }, + "receive": { + "title": "SYNORを受信", + "yourAddress": "あなたのアドレス", + "requestPayment": "支払いをリクエスト", + "amountOptional": "金額(オプション)", + "generateLink": "支払いリンクをコピー", + "qrUpdated": "QRコードが{{amount}} SYNORのリクエストに更新されました", + "warning": "SYNORを受け取るにはこのアドレスを共有してください。このアドレスにはSYNORのみを送信してください - 他の暗号通貨は失われます。" + }, + "history": { + "title": "取引履歴", + "received": "受信", + "sent": "送信", + "pending": "保留中", + "confirmed": "確認済み", + "noTransactions": "取引履歴はありません", + "viewOnExplorer": "エクスプローラーで見る" + }, + "settings": { + "title": "設定", + "network": { + "title": "ネットワーク", + "mainnet": "メインネット", + "mainnetDesc": "本番ネットワーク", + "testnet": "テストネット", + "testnetDesc": "テスト用SYNORでテスト", + "devnet": "開発ネット", + "devnetDesc": "ローカル開発", + "mainnetWarning": "メインネットはまだ開始されていません。テストネットまたは開発ネットに切り替えてください。" + }, + "rpcEndpoint": { + "title": "RPCエンドポイント", + "description": "カスタムSynorノードに接続" + }, + "hardwareWallet": { + "title": "ハードウェアウォレット", + "description": "セキュリティを強化するためにLedgerまたはTrezorハードウェアウォレットを接続してください。秘密鍵はデバイスから離れることはありません。", + "connect": "ハードウェアウォレットを接続", + "notSupported": "このブラウザではサポートされていません", + "notSupportedHint": "ハードウェアウォレットのサポートには、デスクトップでChrome、Edge、またはBraveを使用してください。", + "connected": "{{type}}が接続されました", + "disconnect": "切断" + }, + "security": { + "title": "セキュリティ", + "autoLock": "自動ロック", + "autoLockDescription": "非アクティブ後にウォレットをロック", + "minutes": "{{count}}分", + "hour": "1時間", + "never": "なし" + }, + "language": { + "title": "言語", + "description": "希望の言語を選択" + }, + "dangerZone": { + "title": "危険ゾーン", + "deleteWallet": "ウォレットを削除", + "deleteWarning": "このデバイスからウォレットが完全に削除されます。復元フレーズをバックアップしていることを確認してください。", + "typeToConfirm": "確認するには\"DELETE\"と入力してください", + "confirmDelete": "削除を確認" + }, + "version": "Synor Webウォレット v{{version}}" + }, + "errors": { + "walletNotUnlocked": "ウォレットがロック解除されていません", + "transactionFailed": "取引に失敗しました", + "connectionFailed": "接続に失敗しました", + "invalidPassword": "無効なパスワード" + } +} diff --git a/apps/web/src/i18n/locales/ko.json b/apps/web/src/i18n/locales/ko.json new file mode 100644 index 0000000..bb5e43e --- /dev/null +++ b/apps/web/src/i18n/locales/ko.json @@ -0,0 +1,151 @@ +{ + "common": { + "appName": "Synor 지갑", + "loading": "로딩 중...", + "copy": "복사", + "copied": "복사됨!", + "cancel": "취소", + "confirm": "확인", + "save": "저장", + "close": "닫기", + "back": "뒤로", + "next": "다음", + "done": "완료", + "error": "오류", + "success": "성공", + "warning": "경고", + "synor": "SYNOR" + }, + "welcome": { + "title": "Synor에 오신 것을 환영합니다", + "subtitle": "양자 보안 암호화폐 지갑", + "createWallet": "새 지갑 만들기", + "recoverWallet": "지갑 복구", + "description": "Synor는 Ed25519 + Dilithium3 하이브리드 서명을 사용한 최초의 포스트 양자 보안 암호화폐입니다." + }, + "createWallet": { + "title": "새 지갑 만들기", + "step1Title": "복구 문구 기록", + "step1Description": "이 24개의 단어를 순서대로 적어두세요. 이것이 지갑 백업입니다.", + "step2Title": "복구 문구 확인", + "step2Description": "저장했는지 확인하기 위해 올바른 순서로 단어를 선택하세요.", + "step3Title": "비밀번호 설정", + "step3Description": "이 기기에서 지갑을 암호화할 비밀번호를 만드세요.", + "passwordLabel": "비밀번호", + "passwordConfirmLabel": "비밀번호 확인", + "passwordHint": "최소 8자", + "warningTitle": "중요", + "warningText": "복구 문구를 절대 공유하지 마세요. 이 단어를 가진 사람은 누구나 자금에 접근할 수 있습니다.", + "createButton": "지갑 만들기" + }, + "recoverWallet": { + "title": "지갑 복구", + "description": "지갑을 복원하려면 24단어 복구 문구를 입력하세요.", + "phraseLabel": "복구 문구", + "phrasePlaceholder": "24개의 단어를 공백으로 구분하여 입력", + "invalidPhrase": "유효하지 않은 복구 문구", + "recoverButton": "지갑 복구" + }, + "dashboard": { + "title": "지갑", + "balance": "잔액", + "confirmedBalance": "확인됨", + "pendingBalance": "대기 중", + "send": "보내기", + "receive": "받기", + "history": "내역", + "noTransactions": "거래 내역 없음", + "recentTransactions": "최근 거래" + }, + "send": { + "title": "SYNOR 보내기", + "recipientLabel": "받는 주소", + "recipientPlaceholder": "synor:qz...", + "amountLabel": "금액", + "maxButton": "최대", + "networkFee": "네트워크 수수료", + "sendButton": "거래 보내기", + "sending": "전송 중...", + "scanQR": "QR 코드 스캔", + "invalidAddress": "유효하지 않은 주소 형식. \"synor:\"로 시작해야 합니다", + "invalidAmount": "유효하지 않은 금액", + "insufficientFunds": "잔액 부족", + "success": { + "title": "거래 전송됨", + "description": "거래가 네트워크에 제출되었습니다.", + "txIdLabel": "거래 ID", + "sendAnother": "다른 거래 보내기", + "backToWallet": "지갑으로 돌아가기" + } + }, + "receive": { + "title": "SYNOR 받기", + "yourAddress": "내 주소", + "requestPayment": "결제 요청", + "amountOptional": "금액 (선택사항)", + "generateLink": "결제 링크 복사", + "qrUpdated": "QR 코드가 {{amount}} SYNOR 요청으로 업데이트됨", + "warning": "SYNOR를 받으려면 이 주소를 공유하세요. 이 주소로는 SYNOR만 보내세요 - 다른 암호화폐는 손실됩니다." + }, + "history": { + "title": "거래 내역", + "received": "받음", + "sent": "보냄", + "pending": "대기 중", + "confirmed": "확인됨", + "noTransactions": "거래 내역 없음", + "viewOnExplorer": "탐색기에서 보기" + }, + "settings": { + "title": "설정", + "network": { + "title": "네트워크", + "mainnet": "메인넷", + "mainnetDesc": "프로덕션 네트워크", + "testnet": "테스트넷", + "testnetDesc": "테스트 SYNOR로 테스트", + "devnet": "개발넷", + "devnetDesc": "로컬 개발", + "mainnetWarning": "메인넷이 아직 출시되지 않았습니다. 테스트넷 또는 개발넷으로 전환하세요." + }, + "rpcEndpoint": { + "title": "RPC 엔드포인트", + "description": "커스텀 Synor 노드에 연결" + }, + "hardwareWallet": { + "title": "하드웨어 지갑", + "description": "향상된 보안을 위해 Ledger 또는 Trezor 하드웨어 지갑을 연결하세요. 개인 키는 기기를 떠나지 않습니다.", + "connect": "하드웨어 지갑 연결", + "notSupported": "이 브라우저에서 지원되지 않음", + "notSupportedHint": "하드웨어 지갑 지원을 위해 데스크톱에서 Chrome, Edge 또는 Brave를 사용하세요.", + "connected": "{{type}} 연결됨", + "disconnect": "연결 해제" + }, + "security": { + "title": "보안", + "autoLock": "자동 잠금", + "autoLockDescription": "비활성 후 지갑 잠금", + "minutes": "{{count}}분", + "hour": "1시간", + "never": "안 함" + }, + "language": { + "title": "언어", + "description": "선호하는 언어 선택" + }, + "dangerZone": { + "title": "위험 영역", + "deleteWallet": "지갑 삭제", + "deleteWarning": "이 기기에서 지갑이 영구적으로 삭제됩니다. 복구 문구를 백업했는지 확인하세요.", + "typeToConfirm": "확인하려면 \"DELETE\"를 입력하세요", + "confirmDelete": "삭제 확인" + }, + "version": "Synor 웹 지갑 v{{version}}" + }, + "errors": { + "walletNotUnlocked": "지갑이 잠금 해제되지 않음", + "transactionFailed": "거래 실패", + "connectionFailed": "연결 실패", + "invalidPassword": "유효하지 않은 비밀번호" + } +} diff --git a/apps/web/src/i18n/locales/ru.json b/apps/web/src/i18n/locales/ru.json new file mode 100644 index 0000000..3391acb --- /dev/null +++ b/apps/web/src/i18n/locales/ru.json @@ -0,0 +1,151 @@ +{ + "common": { + "appName": "Synor Кошелёк", + "loading": "Загрузка...", + "copy": "Копировать", + "copied": "Скопировано!", + "cancel": "Отмена", + "confirm": "Подтвердить", + "save": "Сохранить", + "close": "Закрыть", + "back": "Назад", + "next": "Далее", + "done": "Готово", + "error": "Ошибка", + "success": "Успех", + "warning": "Предупреждение", + "synor": "SYNOR" + }, + "welcome": { + "title": "Добро пожаловать в Synor", + "subtitle": "Квантово-безопасный криптовалютный кошелёк", + "createWallet": "Создать новый кошелёк", + "recoverWallet": "Восстановить кошелёк", + "description": "Synor - первая криптовалюта с постквантовой безопасностью, использующая гибридные подписи Ed25519 + Dilithium3." + }, + "createWallet": { + "title": "Создать новый кошелёк", + "step1Title": "Запишите фразу восстановления", + "step1Description": "Запишите эти 24 слова по порядку. Это резервная копия вашего кошелька.", + "step2Title": "Подтвердите фразу восстановления", + "step2Description": "Выберите слова в правильном порядке, чтобы подтвердить их сохранение.", + "step3Title": "Установите пароль", + "step3Description": "Создайте пароль для шифрования кошелька на этом устройстве.", + "passwordLabel": "Пароль", + "passwordConfirmLabel": "Подтвердите пароль", + "passwordHint": "Минимум 8 символов", + "warningTitle": "Важно", + "warningText": "Никогда не делитесь фразой восстановления. Любой, у кого есть эти слова, может получить доступ к вашим средствам.", + "createButton": "Создать кошелёк" + }, + "recoverWallet": { + "title": "Восстановить кошелёк", + "description": "Введите фразу восстановления из 24 слов для восстановления кошелька.", + "phraseLabel": "Фраза восстановления", + "phrasePlaceholder": "Введите 24 слова, разделённых пробелами", + "invalidPhrase": "Неверная фраза восстановления", + "recoverButton": "Восстановить кошелёк" + }, + "dashboard": { + "title": "Кошелёк", + "balance": "Баланс", + "confirmedBalance": "Подтверждённый", + "pendingBalance": "Ожидающий", + "send": "Отправить", + "receive": "Получить", + "history": "История", + "noTransactions": "Транзакций пока нет", + "recentTransactions": "Последние транзакции" + }, + "send": { + "title": "Отправить SYNOR", + "recipientLabel": "Адрес получателя", + "recipientPlaceholder": "synor:qz...", + "amountLabel": "Сумма", + "maxButton": "Макс", + "networkFee": "Сетевая комиссия", + "sendButton": "Отправить транзакцию", + "sending": "Отправка...", + "scanQR": "Сканировать QR-код", + "invalidAddress": "Неверный формат адреса. Должен начинаться с \"synor:\"", + "invalidAmount": "Неверная сумма", + "insufficientFunds": "Недостаточно средств", + "success": { + "title": "Транзакция отправлена", + "description": "Ваша транзакция была отправлена в сеть.", + "txIdLabel": "ID транзакции", + "sendAnother": "Отправить ещё", + "backToWallet": "Вернуться в кошелёк" + } + }, + "receive": { + "title": "Получить SYNOR", + "yourAddress": "Ваш адрес", + "requestPayment": "Запросить платёж", + "amountOptional": "Сумма (необязательно)", + "generateLink": "Скопировать ссылку для оплаты", + "qrUpdated": "QR-код обновлён для запроса {{amount}} SYNOR", + "warning": "Поделитесь этим адресом для получения SYNOR. Отправляйте на этот адрес только SYNOR - другие криптовалюты будут потеряны." + }, + "history": { + "title": "История транзакций", + "received": "Получено", + "sent": "Отправлено", + "pending": "Ожидается", + "confirmed": "Подтверждено", + "noTransactions": "Транзакций пока нет", + "viewOnExplorer": "Посмотреть в обозревателе" + }, + "settings": { + "title": "Настройки", + "network": { + "title": "Сеть", + "mainnet": "Основная сеть", + "mainnetDesc": "Рабочая сеть", + "testnet": "Тестовая сеть", + "testnetDesc": "Тест с тестовыми SYNOR", + "devnet": "Сеть разработки", + "devnetDesc": "Локальная разработка", + "mainnetWarning": "Основная сеть ещё не запущена. Переключитесь на тестовую или сеть разработки." + }, + "rpcEndpoint": { + "title": "RPC-точка", + "description": "Подключиться к пользовательскому узлу Synor" + }, + "hardwareWallet": { + "title": "Аппаратный кошелёк", + "description": "Подключите аппаратный кошелёк Ledger или Trezor для повышенной безопасности. Ваши приватные ключи никогда не покидают устройство.", + "connect": "Подключить аппаратный кошелёк", + "notSupported": "Не поддерживается в этом браузере", + "notSupportedHint": "Используйте Chrome, Edge или Brave на компьютере для поддержки аппаратных кошельков.", + "connected": "{{type}} подключён", + "disconnect": "Отключить" + }, + "security": { + "title": "Безопасность", + "autoLock": "Автоблокировка", + "autoLockDescription": "Блокировать кошелёк после бездействия", + "minutes": "{{count}} минут", + "hour": "1 час", + "never": "Никогда" + }, + "language": { + "title": "Язык", + "description": "Выберите предпочитаемый язык" + }, + "dangerZone": { + "title": "Опасная зона", + "deleteWallet": "Удалить кошелёк", + "deleteWarning": "Это навсегда удалит кошелёк с этого устройства. Убедитесь, что у вас есть резервная копия фразы восстановления.", + "typeToConfirm": "Введите \"DELETE\" для подтверждения", + "confirmDelete": "Подтвердить удаление" + }, + "version": "Synor Web Кошелёк v{{version}}" + }, + "errors": { + "walletNotUnlocked": "Кошелёк не разблокирован", + "transactionFailed": "Транзакция не удалась", + "connectionFailed": "Ошибка подключения", + "invalidPassword": "Неверный пароль" + } +} diff --git a/apps/web/src/i18n/locales/zh.json b/apps/web/src/i18n/locales/zh.json new file mode 100644 index 0000000..8b0f833 --- /dev/null +++ b/apps/web/src/i18n/locales/zh.json @@ -0,0 +1,151 @@ +{ + "common": { + "appName": "Synor 钱包", + "loading": "加载中...", + "copy": "复制", + "copied": "已复制!", + "cancel": "取消", + "confirm": "确认", + "save": "保存", + "close": "关闭", + "back": "返回", + "next": "下一步", + "done": "完成", + "error": "错误", + "success": "成功", + "warning": "警告", + "synor": "SYNOR" + }, + "welcome": { + "title": "欢迎使用 Synor", + "subtitle": "量子安全加密货币钱包", + "createWallet": "创建新钱包", + "recoverWallet": "恢复钱包", + "description": "Synor 是首个采用混合 Ed25519 + Dilithium3 签名的后量子安全加密货币。" + }, + "createWallet": { + "title": "创建新钱包", + "step1Title": "记录恢复短语", + "step1Description": "按顺序写下这24个单词。这是您的钱包备份。", + "step2Title": "验证恢复短语", + "step2Description": "按正确顺序选择单词以验证您已保存。", + "step3Title": "设置密码", + "step3Description": "创建密码以在此设备上加密您的钱包。", + "passwordLabel": "密码", + "passwordConfirmLabel": "确认密码", + "passwordHint": "至少8个字符", + "warningTitle": "重要提示", + "warningText": "切勿分享您的恢复短语。任何拥有这些单词的人都可以访问您的资金。", + "createButton": "创建钱包" + }, + "recoverWallet": { + "title": "恢复钱包", + "description": "输入您的24个单词恢复短语以恢复您的钱包。", + "phraseLabel": "恢复短语", + "phrasePlaceholder": "输入您的24个单词,用空格分隔", + "invalidPhrase": "无效的恢复短语", + "recoverButton": "恢复钱包" + }, + "dashboard": { + "title": "钱包", + "balance": "余额", + "confirmedBalance": "已确认", + "pendingBalance": "待处理", + "send": "发送", + "receive": "接收", + "history": "历史", + "noTransactions": "暂无交易", + "recentTransactions": "最近交易" + }, + "send": { + "title": "发送 SYNOR", + "recipientLabel": "收款地址", + "recipientPlaceholder": "synor:qz...", + "amountLabel": "金额", + "maxButton": "最大", + "networkFee": "网络费用", + "sendButton": "发送交易", + "sending": "发送中...", + "scanQR": "扫描二维码", + "invalidAddress": "无效的地址格式。必须以 \"synor:\" 开头", + "invalidAmount": "无效金额", + "insufficientFunds": "余额不足", + "success": { + "title": "交易已发送", + "description": "您的交易已提交到网络。", + "txIdLabel": "交易ID", + "sendAnother": "发送另一笔", + "backToWallet": "返回钱包" + } + }, + "receive": { + "title": "接收 SYNOR", + "yourAddress": "您的地址", + "requestPayment": "请求付款", + "amountOptional": "金额(可选)", + "generateLink": "复制付款链接", + "qrUpdated": "二维码已更新为请求 {{amount}} SYNOR", + "warning": "分享此地址以接收 SYNOR。只向此地址发送 SYNOR - 其他加密货币将会丢失。" + }, + "history": { + "title": "交易历史", + "received": "已接收", + "sent": "已发送", + "pending": "待处理", + "confirmed": "已确认", + "noTransactions": "暂无交易", + "viewOnExplorer": "在浏览器中查看" + }, + "settings": { + "title": "设置", + "network": { + "title": "网络", + "mainnet": "主网", + "mainnetDesc": "生产网络", + "testnet": "测试网", + "testnetDesc": "使用测试 SYNOR", + "devnet": "开发网", + "devnetDesc": "本地开发", + "mainnetWarning": "主网尚未上线。请切换到测试网或开发网。" + }, + "rpcEndpoint": { + "title": "RPC 端点", + "description": "连接到自定义 Synor 节点" + }, + "hardwareWallet": { + "title": "硬件钱包", + "description": "连接 Ledger 或 Trezor 硬件钱包以增强安全性。您的私钥永远不会离开设备。", + "connect": "连接硬件钱包", + "notSupported": "此浏览器不支持", + "notSupportedHint": "请使用桌面版 Chrome、Edge 或 Brave 以支持硬件钱包。", + "connected": "{{type}} 已连接", + "disconnect": "断开连接" + }, + "security": { + "title": "安全", + "autoLock": "自动锁定", + "autoLockDescription": "闲置后锁定钱包", + "minutes": "{{count}} 分钟", + "hour": "1 小时", + "never": "从不" + }, + "language": { + "title": "语言", + "description": "选择您的首选语言" + }, + "dangerZone": { + "title": "危险区域", + "deleteWallet": "删除钱包", + "deleteWarning": "这将永久删除此设备上的钱包。请确保您已备份恢复短语。", + "typeToConfirm": "输入 \"DELETE\" 以确认", + "confirmDelete": "确认删除" + }, + "version": "Synor 网页钱包 v{{version}}" + }, + "errors": { + "walletNotUnlocked": "钱包未解锁", + "transactionFailed": "交易失败", + "connectionFailed": "连接失败", + "invalidPassword": "密码无效" + } +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index a814b52..a70538d 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -4,6 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; import App from './App'; import './index.css'; +// Initialize i18n (must be imported before any component that uses it) +import './i18n'; + ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/apps/web/src/pages/Settings.tsx b/apps/web/src/pages/Settings.tsx index dbc70ff..af8829c 100644 --- a/apps/web/src/pages/Settings.tsx +++ b/apps/web/src/pages/Settings.tsx @@ -1,7 +1,9 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { useWalletStore, type Network } from '../store/wallet'; import { HardwareWalletConnect } from '../components/HardwareWalletConnect'; +import { LanguageSelector } from '../components/LanguageSelector'; import { isHardwareWalletSupported, type HardwareWalletAccount, @@ -9,6 +11,7 @@ import { } from '../lib/hardware-wallet'; export default function SettingsPage() { + const { t } = useTranslation(); const { network, rpcEndpoint, @@ -177,26 +180,32 @@ export default function SettingsPage() { {/* Security */}
-

Security

+

{t('settings.security.title')}

-
Auto-lock
+
{t('settings.security.autoLock')}
- Lock wallet after inactivity + {t('settings.security.autoLockDescription')}
+ {/* Language */} +
+

{t('settings.language.title')}

+ +
+ {/* Danger Zone */}

Danger Zone