const SUPPORTED_LANGS = ['en', 'it', 'es', 'fr', 'de', 'ru', 'zh']; const LANG_STORAGE_KEY = 'plm_lang'; // date-locale used for toLocaleDateString, keyed by the same language codes const DATE_LOCALES = { en: 'en-US', it: 'it-IT', es: 'es-ES', fr: 'fr-FR', de: 'de-DE', ru: 'ru-RU', zh: 'zh-CN' }; const TRANSLATIONS = { en: { 'nav.ariaSections': 'Sections', 'nav.guideTitle': 'Guide', 'nav.guideAria': 'Open the user guide', 'nav.bugReport': 'Report a bug', 'nav.logoutTitle': 'Log out', 'nav.logoutAria': 'Log out of your account', 'nav.deposit': 'Deposit', 'nav.bet': 'Bet', 'nav.withdraw': 'Withdraw', 'nav.profile': 'Profile', 'chain.connecting': 'Connecting…', 'chain.block': 'Block {n}', 'chain.connectionLost': 'Connection to server lost — retrying…', 'maintenance.banner': 'Scheduled maintenance: the current round completes normally (winner included), but the next round will not open until maintenance ends.', 'hero.lead': 'Deposit PLM, join the round with a fixed entry fee, and if your number is drawn you win the jackpot.', 'hero.step1.title': '1. Deposit', 'hero.step1.hint': 'Get your own personal PLM address, yours forever', 'hero.step2.title': '2. Play', 'hero.step2.hint': 'A fixed-cost bet to enter the current round', 'hero.step3.title': '3. Win', 'hero.step3.hint': 'Drawn from a block hash, jackpot credited instantly', 'trust.fixedRate': 'Declared fixed entry fee', 'trust.blockHash': 'Drawn from a block hash', 'trust.freeWithdraw': 'Withdraw freely at any time', 'auth.tabLogin': 'Login', 'auth.tabRegister': 'Register', 'auth.username': 'Username', 'auth.password': 'Password', 'auth.passwordConfirm': 'Confirm password', 'auth.loginBtn': 'Log in', 'auth.registerBtn': 'Create account', 'round.players': 'Players', 'round.jackpot': 'Jackpot', 'round.status.open': 'open', 'round.status.closing': 'closing', 'round.status.drawing': 'drawing in progress', 'round.status.paying_out': 'paying out the winner', 'round.title': 'Round #{id} — {status}', 'round.none': 'No active round', 'chain.status.waiting': 'Waiting for the next round', 'chain.status.open': 'Round open', 'chain.status.closing': 'Round closed — waiting for bet confirmations', 'chain.status.drawing': 'Drawing in progress', 'chain.status.paying_out': 'Paying out the winner', 'draw.defaultLabel': 'Drawing the winner…', 'draw.closing': 'Round closed — waiting for the last bet to confirm before drawing the winner…', 'draw.drawing': 'Waiting for the next block to draw the winner…', 'draw.payingOutBlock': 'Winner drawn from block #{height} — paying out the winner…', 'draw.payingOut': 'Winner drawn — paying out the winner…', 'result.win': '🎉 You won! +{amount} PLM', 'result.lose': 'Not a win this time.', 'deposit.balanceTitle': 'Internal balance', 'deposit.balanceHint': 'Updated after 1 network confirmation', 'deposit.refreshBtn': 'Refresh', 'deposit.refreshAria': 'Refresh balance', 'deposit.addressTitle': 'Deposit address', 'deposit.addressHint': 'This also receives any winnings', 'deposit.copyAria': 'Copy address', 'deposit.qrAlt': 'QR code of the deposit address', 'bet.title': 'Bet', 'bet.hint': 'Fixed entry to the current round', 'bet.button': 'Place bet ({amount} PLM)', 'bet.buttonNoAmount': 'Place bet', 'withdraw.title': 'Withdrawal', 'withdraw.hint': 'Send funds to an external PLM address', 'withdraw.addressLabel': 'External address', 'withdraw.addressHint': 'Only P2WPKH bech32 addresses (starting with plm1q...). Legacy (P...) or P2SH addresses are not supported.', 'withdraw.amountLabel': 'Amount (PLM)', 'withdraw.amountPlaceholder': 'e.g. 2', 'withdraw.fullAmountPrefix': 'Withdraw the full amount (', 'withdraw.fullAmountSuffix': ' PLM)', 'withdraw.button': 'Withdraw', 'profile.title': 'Profile', 'profile.hint': 'Your account information', 'profile.usernameLabel': 'Username', 'profile.addressLabel': 'Deposit address', 'profile.balanceLabel': 'Internal balance', 'profile.createdLabel': 'User since', 'settings.title': 'Settings', 'settings.hint': "Change your account's password", 'settings.currentPasswordLabel': 'Current password', 'settings.newPasswordLabel': 'New password', 'settings.newPasswordConfirmLabel': 'Confirm new password', 'settings.updateBtn': 'Update password', 'toast.passwordMismatch': 'Passwords do not match.', 'toast.accountCreated': 'Account created.', 'toast.loginSuccess': 'Logged in successfully.', 'toast.requestTimeout': 'Request to the server timed out.', 'toast.addressCopied': 'Address copied.', 'toast.copyFailed': "Couldn't copy automatically.", 'toast.newPasswordMismatch': 'The new passwords do not match.', 'toast.passwordTooShort': 'The new password must be at least 8 characters.', 'toast.passwordUpdated': 'Password updated.', 'toast.betPlaced': 'Bet placed on round #{id}.', 'toast.roundWon': 'You won round #{id}! +{amount} PLM', 'toast.withdrawSent': 'Withdrawal sent.', // Keyed by the `code` the API returns in its structured error detail // (app/api/errors.py). A code with no entry here falls back to the API's // own English `message`, so a new server-side error is never a blank toast. 'error.network_unavailable': 'Not connected to the network, please try again shortly.', 'error.no_round_open': 'No round is open right now, please try again shortly.', 'error.round_closing': 'The current round is closing, please try again shortly.', 'error.already_betting': 'You already have an active bet in the current round.', 'error.insufficient_balance': 'Insufficient balance.', 'error.balance_pending_confirmation': 'You have {pending_plm} PLM pending confirmation — it is not spendable yet.', 'error.amount_below_network_fee': 'The amount is too small to cover the network fee.', 'error.invalid_address': 'Not a valid PLM address (it must start with plm1q…).', 'error.amount_below_minimum': 'The minimum withdrawal amount is {minimum_plm} PLM.', 'error.current_password_incorrect': 'The current password is incorrect.', 'error.password_too_short': 'The new password must be at least {minimum} characters.', 'error.username_taken': 'This username is already taken.', 'error.invalid_credentials': 'Wrong username or password.', 'error.derivation_index_conflict': 'Registration failed, please try again.', 'error.session_expired': 'Session expired, please log in again.', 'error.invalid_request': 'Invalid request, please check the entered data.', 'error.invalid_amount': 'Enter an amount greater than zero.', 'error.broadcast_failed': 'The network refused the transaction. Please try again shortly.', 'error.amount_below_dust_limit': 'The amount is too small to be sent.', 'error.withdrawal_to_own_address': 'That is your own deposit address — withdraw to an external wallet.', 'error.internal_error': 'Unexpected server error. Please try again shortly.', 'error.guide_unavailable': 'The guide is not available right now.', 'error.rate_limited': 'Too many attempts, please try again in {retry_after_seconds} seconds.', 'loading.creating': 'Creating…', 'loading.loggingIn': 'Logging in…', 'loading.sendingBet': 'Placing bet…', 'loading.updating': 'Updating…', 'loading.refreshing': '…', 'loading.sending': 'Sending…', }, it: { 'nav.ariaSections': 'Sezioni', 'nav.guideTitle': 'Guida', 'nav.guideAria': 'Apri la guida utente', 'nav.bugReport': 'Segnala un bug', 'nav.logoutTitle': 'Esci', 'nav.logoutAria': "Esci dall'account", 'nav.deposit': 'Deposito', 'nav.bet': 'Bet', 'nav.withdraw': 'Prelievo', 'nav.profile': 'Profilo', 'chain.connecting': 'Connessione…', 'chain.block': 'Blocco {n}', 'chain.connectionLost': 'Connessione al server persa — riprovo…', 'maintenance.banner': 'Manutenzione in programma: il round in corso viene completato regolarmente (vincitore incluso), ma il round successivo non si aprirà finché la manutenzione non sarà terminata.', 'hero.lead': 'Deposita PLM, entra nel round con una quota fissa, e se viene estratto il tuo numero vinci il montepremi.', 'hero.step1.title': '1. Deposita', 'hero.step1.hint': 'Ricevi un indirizzo PLM personale, tuo per sempre', 'hero.step2.title': '2. Gioca', 'hero.step2.hint': 'Una bet a quota fissa per entrare nel round corrente', 'hero.step3.title': '3. Vinci', 'hero.step3.hint': 'Estrazione dal blocco, montepremi accreditato subito', 'trust.fixedRate': 'Quota fissa dichiarata', 'trust.blockHash': 'Estrazione da hash di blocco', 'trust.freeWithdraw': 'Prelievo libero in ogni momento', 'auth.tabLogin': 'Login', 'auth.tabRegister': 'Registrati', 'auth.username': 'Username', 'auth.password': 'Password', 'auth.passwordConfirm': 'Conferma password', 'auth.loginBtn': 'Accedi', 'auth.registerBtn': 'Crea account', 'round.players': 'Giocatori', 'round.jackpot': 'Jackpot', 'round.status.open': 'aperto', 'round.status.closing': 'in chiusura', 'round.status.drawing': 'estrazione in corso', 'round.status.paying_out': 'pagamento al vincitore in corso', 'round.title': 'Round #{id} — {status}', 'round.none': 'Nessun round attivo', 'chain.status.waiting': 'In attesa del prossimo round', 'chain.status.open': 'Round aperto', 'chain.status.closing': 'Round chiuso — attesa conferma puntate', 'chain.status.drawing': 'Estrazione in corso', 'chain.status.paying_out': 'Pagamento al vincitore in corso', 'draw.defaultLabel': 'Estrazione del vincitore in corso…', 'draw.closing': "Round chiuso — in attesa di conferma dell'ultima giocata prima di estrarre il vincitore…", 'draw.drawing': 'In attesa del prossimo blocco per estrarre il vincitore…', 'draw.payingOutBlock': 'Vincitore estratto dal blocco #{height} — pagamento al vincitore in corso…', 'draw.payingOut': 'Vincitore estratto — pagamento al vincitore in corso…', 'result.win': '🎉 Hai vinto! +{amount} PLM', 'result.lose': 'Non hai vinto questa volta.', 'deposit.balanceTitle': 'Saldo interno', 'deposit.balanceHint': 'Aggiornato dopo 1 conferma sulla rete', 'deposit.refreshBtn': 'Aggiorna', 'deposit.refreshAria': 'Aggiorna saldo', 'deposit.addressTitle': 'Indirizzo di deposito', 'deposit.addressHint': "È anche l'indirizzo su cui ricevi eventuali vincite", 'deposit.copyAria': 'Copia indirizzo', 'deposit.qrAlt': "QR code dell'indirizzo di deposito", 'bet.title': 'Bet', 'bet.hint': 'Ingresso fisso al round corrente', 'bet.button': 'Piazza bet ({amount} PLM)', 'bet.buttonNoAmount': 'Piazza bet', 'withdraw.title': 'Withdrawal', 'withdraw.hint': 'Invia fondi a un indirizzo PLM esterno', 'withdraw.addressLabel': 'Indirizzo esterno', 'withdraw.addressHint': 'Solo indirizzi P2WPKH bech32 (quelli che iniziano con plm1q...). Indirizzi legacy (P...) o P2SH non sono supportati.', 'withdraw.amountLabel': 'Importo (PLM)', 'withdraw.amountPlaceholder': 'es. 2', 'withdraw.fullAmountPrefix': "Preleva l'intero importo (", 'withdraw.fullAmountSuffix': ' PLM)', 'withdraw.button': 'Preleva', 'profile.title': 'Profilo', 'profile.hint': 'Le tue informazioni account', 'profile.usernameLabel': 'Username', 'profile.addressLabel': 'Indirizzo di deposito', 'profile.balanceLabel': 'Saldo interno', 'profile.createdLabel': 'Utente dal', 'settings.title': 'Impostazioni', 'settings.hint': 'Cambia la password del tuo account', 'settings.currentPasswordLabel': 'Password attuale', 'settings.newPasswordLabel': 'Nuova password', 'settings.newPasswordConfirmLabel': 'Conferma nuova password', 'settings.updateBtn': 'Aggiorna password', 'toast.passwordMismatch': 'Le password non coincidono.', 'toast.accountCreated': 'Account creato.', 'toast.loginSuccess': 'Accesso riuscito.', 'toast.requestTimeout': 'Richiesta al server scaduta.', 'toast.addressCopied': 'Indirizzo copiato.', 'toast.copyFailed': 'Impossibile copiare automaticamente.', 'toast.newPasswordMismatch': 'Le nuove password non coincidono.', 'toast.passwordTooShort': 'La nuova password deve avere almeno 8 caratteri.', 'toast.passwordUpdated': 'Password aggiornata.', 'toast.betPlaced': 'Bet piazzata sul round #{id}.', 'toast.roundWon': 'Hai vinto il round #{id}! +{amount} PLM', 'toast.withdrawSent': 'Withdrawal inviato.', 'error.network_unavailable': 'Nessuna connessione alla rete, riprova tra poco.', 'error.no_round_open': 'Nessun round aperto in questo momento, riprova tra poco.', 'error.round_closing': 'Il round corrente si sta chiudendo, riprova tra poco.', 'error.already_betting': 'Hai già una bet attiva nel round corrente.', 'error.insufficient_balance': 'Saldo insufficiente.', 'error.balance_pending_confirmation': 'Hai {pending_plm} PLM in attesa di conferma — non ancora disponibili per la spesa.', 'error.amount_below_network_fee': "L'importo è troppo basso per coprire la fee di rete.", 'error.invalid_address': 'Indirizzo PLM non valido (deve iniziare con plm1q…).', 'error.amount_below_minimum': "L'importo minimo di prelievo è {minimum_plm} PLM.", 'error.current_password_incorrect': 'La password attuale non è corretta.', 'error.password_too_short': 'La nuova password deve avere almeno {minimum} caratteri.', 'error.username_taken': 'Questo username è già stato preso.', 'error.invalid_credentials': 'Username o password errati.', 'error.derivation_index_conflict': 'Registrazione non riuscita, riprova.', 'error.session_expired': 'Sessione scaduta, accedi di nuovo.', 'error.invalid_request': 'Richiesta non valida, controlla i dati inseriti.', 'error.invalid_amount': 'Inserisci un importo maggiore di zero.', 'error.broadcast_failed': 'La rete ha rifiutato la transazione. Riprova tra poco.', 'error.amount_below_dust_limit': "L'importo è troppo basso per essere inviato.", 'error.withdrawal_to_own_address': 'Questo è il tuo indirizzo di deposito — preleva verso un wallet esterno.', 'error.internal_error': 'Errore inatteso del server. Riprova tra poco.', 'error.guide_unavailable': 'La guida non è disponibile in questo momento.', 'error.rate_limited': 'Troppi tentativi, riprova tra {retry_after_seconds} secondi.', 'loading.creating': 'Creazione…', 'loading.loggingIn': 'Accesso…', 'loading.sendingBet': 'Invio bet…', 'loading.updating': 'Aggiornamento…', 'loading.refreshing': '…', 'loading.sending': 'Invio…', }, es: { 'nav.ariaSections': 'Secciones', 'nav.guideTitle': 'Guía', 'nav.guideAria': 'Abrir la guía del usuario', 'nav.bugReport': 'Reportar un error', 'nav.logoutTitle': 'Salir', 'nav.logoutAria': 'Cerrar sesión', 'nav.deposit': 'Depósito', 'nav.bet': 'Apuesta', 'nav.withdraw': 'Retiro', 'nav.profile': 'Perfil', 'chain.connecting': 'Conectando…', 'chain.block': 'Bloque {n}', 'chain.connectionLost': 'Conexión con el servidor perdida — reintentando…', 'maintenance.banner': 'Mantenimiento programado: la ronda actual se completa con normalidad (ganador incluido), pero la siguiente ronda no se abrirá hasta que finalice el mantenimiento.', 'hero.lead': 'Deposita PLM, únete a la ronda con una cuota fija de entrada, y si sale tu número ganas el bote.', 'hero.step1.title': '1. Deposita', 'hero.step1.hint': 'Recibe tu dirección PLM personal, tuya para siempre', 'hero.step2.title': '2. Juega', 'hero.step2.hint': 'Una apuesta a cuota fija para entrar en la ronda actual', 'hero.step3.title': '3. Gana', 'hero.step3.hint': 'Sorteo a partir del hash de un bloque, bote acreditado al instante', 'trust.fixedRate': 'Cuota de entrada fija y declarada', 'trust.blockHash': 'Sorteo a partir del hash de un bloque', 'trust.freeWithdraw': 'Retiro libre en cualquier momento', 'auth.tabLogin': 'Iniciar sesión', 'auth.tabRegister': 'Registrarse', 'auth.username': 'Usuario', 'auth.password': 'Contraseña', 'auth.passwordConfirm': 'Confirmar contraseña', 'auth.loginBtn': 'Entrar', 'auth.registerBtn': 'Crear cuenta', 'round.players': 'Jugadores', 'round.jackpot': 'Bote', 'round.status.open': 'abierta', 'round.status.closing': 'cerrando', 'round.status.drawing': 'sorteo en curso', 'round.status.paying_out': 'pagando al ganador', 'round.title': 'Ronda #{id} — {status}', 'round.none': 'No hay ninguna ronda activa', 'chain.status.waiting': 'Esperando la próxima ronda', 'chain.status.open': 'Ronda abierta', 'chain.status.closing': 'Ronda cerrada — esperando confirmación de apuestas', 'chain.status.drawing': 'Sorteo en curso', 'chain.status.paying_out': 'Pagando al ganador', 'draw.defaultLabel': 'Sorteando al ganador…', 'draw.closing': 'Ronda cerrada — esperando la confirmación de la última apuesta antes de sortear al ganador…', 'draw.drawing': 'Esperando el próximo bloque para sortear al ganador…', 'draw.payingOutBlock': 'Ganador sorteado en el bloque #{height} — pagando al ganador…', 'draw.payingOut': 'Ganador sorteado — pagando al ganador…', 'result.win': '🎉 ¡Has ganado! +{amount} PLM', 'result.lose': 'Esta vez no has ganado.', 'deposit.balanceTitle': 'Saldo interno', 'deposit.balanceHint': 'Actualizado tras 1 confirmación en la red', 'deposit.refreshBtn': 'Actualizar', 'deposit.refreshAria': 'Actualizar saldo', 'deposit.addressTitle': 'Dirección de depósito', 'deposit.addressHint': 'También es la dirección donde recibes tus posibles ganancias', 'deposit.copyAria': 'Copiar dirección', 'deposit.qrAlt': 'Código QR de la dirección de depósito', 'bet.title': 'Apuesta', 'bet.hint': 'Entrada fija a la ronda actual', 'bet.button': 'Realizar apuesta ({amount} PLM)', 'bet.buttonNoAmount': 'Realizar apuesta', 'withdraw.title': 'Retiro', 'withdraw.hint': 'Envía fondos a una dirección PLM externa', 'withdraw.addressLabel': 'Dirección externa', 'withdraw.addressHint': 'Solo direcciones P2WPKH bech32 (las que empiezan con plm1q...). No se admiten direcciones legacy (P...) ni P2SH.', 'withdraw.amountLabel': 'Importe (PLM)', 'withdraw.amountPlaceholder': 'p.ej. 2', 'withdraw.fullAmountPrefix': 'Retirar el importe completo (', 'withdraw.fullAmountSuffix': ' PLM)', 'withdraw.button': 'Retirar', 'profile.title': 'Perfil', 'profile.hint': 'La información de tu cuenta', 'profile.usernameLabel': 'Usuario', 'profile.addressLabel': 'Dirección de depósito', 'profile.balanceLabel': 'Saldo interno', 'profile.createdLabel': 'Usuario desde', 'settings.title': 'Ajustes', 'settings.hint': 'Cambia la contraseña de tu cuenta', 'settings.currentPasswordLabel': 'Contraseña actual', 'settings.newPasswordLabel': 'Nueva contraseña', 'settings.newPasswordConfirmLabel': 'Confirmar nueva contraseña', 'settings.updateBtn': 'Actualizar contraseña', 'toast.passwordMismatch': 'Las contraseñas no coinciden.', 'toast.accountCreated': 'Cuenta creada.', 'toast.loginSuccess': 'Sesión iniciada correctamente.', 'toast.requestTimeout': 'La solicitud al servidor ha caducado.', 'toast.addressCopied': 'Dirección copiada.', 'toast.copyFailed': 'No se pudo copiar automáticamente.', 'toast.newPasswordMismatch': 'Las nuevas contraseñas no coinciden.', 'toast.passwordTooShort': 'La nueva contraseña debe tener al menos 8 caracteres.', 'toast.passwordUpdated': 'Contraseña actualizada.', 'toast.betPlaced': 'Apuesta realizada en la ronda #{id}.', 'toast.roundWon': '¡Has ganado la ronda #{id}! +{amount} PLM', 'toast.withdrawSent': 'Retiro enviado.', 'error.network_unavailable': 'Sin conexión con la red, inténtalo de nuevo en un momento.', 'error.no_round_open': 'No hay ninguna ronda abierta ahora mismo, inténtalo de nuevo en un momento.', 'error.round_closing': 'La ronda actual se está cerrando, inténtalo de nuevo en un momento.', 'error.already_betting': 'Ya tienes una apuesta activa en la ronda actual.', 'error.insufficient_balance': 'Saldo insuficiente.', 'error.balance_pending_confirmation': 'Tienes {pending_plm} PLM pendientes de confirmación — todavía no se pueden gastar.', 'error.amount_below_network_fee': 'El importe es demasiado pequeño para cubrir la comisión de red.', 'error.invalid_address': 'Dirección PLM no válida (debe empezar por plm1q…).', 'error.amount_below_minimum': 'El importe mínimo de retiro es {minimum_plm} PLM.', 'error.current_password_incorrect': 'La contraseña actual no es correcta.', 'error.password_too_short': 'La nueva contraseña debe tener al menos {minimum} caracteres.', 'error.username_taken': 'Este nombre de usuario ya está en uso.', 'error.invalid_credentials': 'Usuario o contraseña incorrectos.', 'error.derivation_index_conflict': 'No se pudo completar el registro, inténtalo de nuevo.', 'error.session_expired': 'Sesión caducada, vuelve a iniciar sesión.', 'error.invalid_request': 'Solicitud no válida, revisa los datos introducidos.', 'error.invalid_amount': 'Introduce un importe mayor que cero.', 'error.broadcast_failed': 'La red rechazó la transacción. Inténtalo de nuevo en un momento.', 'error.amount_below_dust_limit': 'El importe es demasiado pequeño para enviarse.', 'error.withdrawal_to_own_address': 'Esa es tu propia dirección de depósito — retira a una cartera externa.', 'error.internal_error': 'Error inesperado del servidor. Inténtalo de nuevo en un momento.', 'error.guide_unavailable': 'La guía no está disponible en este momento.', 'error.rate_limited': 'Demasiados intentos, inténtalo de nuevo en {retry_after_seconds} segundos.', 'loading.creating': 'Creando…', 'loading.loggingIn': 'Entrando…', 'loading.sendingBet': 'Enviando apuesta…', 'loading.updating': 'Actualizando…', 'loading.refreshing': '…', 'loading.sending': 'Enviando…', }, fr: { 'nav.ariaSections': 'Sections', 'nav.guideTitle': 'Guide', 'nav.guideAria': "Ouvrir le guide de l'utilisateur", 'nav.bugReport': 'Signaler un bug', 'nav.logoutTitle': 'Se déconnecter', 'nav.logoutAria': 'Se déconnecter du compte', 'nav.deposit': 'Dépôt', 'nav.bet': 'Mise', 'nav.withdraw': 'Retrait', 'nav.profile': 'Profil', 'chain.connecting': 'Connexion…', 'chain.block': 'Bloc {n}', 'chain.connectionLost': 'Connexion au serveur perdue — nouvelle tentative…', 'maintenance.banner': "Maintenance programmée : le round en cours se termine normalement (gagnant inclus), mais le round suivant ne s'ouvrira qu'une fois la maintenance terminée.", 'hero.lead': 'Déposez des PLM, rejoignez le round avec une mise fixe, et si votre numéro est tiré vous remportez le jackpot.', 'hero.step1.title': '1. Déposez', 'hero.step1.hint': 'Recevez votre adresse PLM personnelle, à vous pour toujours', 'hero.step2.title': '2. Jouez', 'hero.step2.hint': 'Une mise à coût fixe pour entrer dans le round en cours', 'hero.step3.title': '3. Gagnez', 'hero.step3.hint': "Tirage à partir du hash d'un bloc, jackpot crédité instantanément", 'trust.fixedRate': "Mise d'entrée fixe et déclarée", 'trust.blockHash': "Tirage à partir du hash d'un bloc", 'trust.freeWithdraw': 'Retrait libre à tout moment', 'auth.tabLogin': 'Connexion', 'auth.tabRegister': "S'inscrire", 'auth.username': "Nom d'utilisateur", 'auth.password': 'Mot de passe', 'auth.passwordConfirm': 'Confirmer le mot de passe', 'auth.loginBtn': 'Se connecter', 'auth.registerBtn': 'Créer un compte', 'round.players': 'Joueurs', 'round.jackpot': 'Jackpot', 'round.status.open': 'ouvert', 'round.status.closing': 'en fermeture', 'round.status.drawing': 'tirage en cours', 'round.status.paying_out': 'paiement du gagnant en cours', 'round.title': 'Round #{id} — {status}', 'round.none': 'Aucun round actif', 'chain.status.waiting': 'En attente du prochain round', 'chain.status.open': 'Round ouvert', 'chain.status.closing': 'Round fermé — attente de confirmation des mises', 'chain.status.drawing': 'Tirage en cours', 'chain.status.paying_out': 'Paiement du gagnant en cours', 'draw.defaultLabel': 'Tirage du gagnant en cours…', 'draw.closing': 'Round fermé — en attente de la confirmation de la dernière mise avant de tirer le gagnant…', 'draw.drawing': 'En attente du prochain bloc pour tirer le gagnant…', 'draw.payingOutBlock': 'Gagnant tiré au bloc #{height} — paiement du gagnant en cours…', 'draw.payingOut': 'Gagnant tiré — paiement du gagnant en cours…', 'result.win': '🎉 Vous avez gagné ! +{amount} PLM', 'result.lose': "Pas de gain cette fois-ci.", 'deposit.balanceTitle': 'Solde interne', 'deposit.balanceHint': 'Mis à jour après 1 confirmation sur le réseau', 'deposit.refreshBtn': 'Actualiser', 'deposit.refreshAria': 'Actualiser le solde', 'deposit.addressTitle': 'Adresse de dépôt', 'deposit.addressHint': "C'est aussi l'adresse sur laquelle vous recevez d'éventuels gains", 'deposit.copyAria': "Copier l'adresse", 'deposit.qrAlt': "Code QR de l'adresse de dépôt", 'bet.title': 'Mise', 'bet.hint': 'Entrée fixe pour le round en cours', 'bet.button': 'Placer une mise ({amount} PLM)', 'bet.buttonNoAmount': 'Placer une mise', 'withdraw.title': 'Retrait', 'withdraw.hint': 'Envoyez des fonds vers une adresse PLM externe', 'withdraw.addressLabel': 'Adresse externe', 'withdraw.addressHint': "Uniquement les adresses P2WPKH bech32 (celles qui commencent par plm1q...). Les adresses legacy (P...) ou P2SH ne sont pas prises en charge.", 'withdraw.amountLabel': 'Montant (PLM)', 'withdraw.amountPlaceholder': 'ex. 2', 'withdraw.fullAmountPrefix': 'Retirer le montant total (', 'withdraw.fullAmountSuffix': ' PLM)', 'withdraw.button': 'Retirer', 'profile.title': 'Profil', 'profile.hint': 'Les informations de votre compte', 'profile.usernameLabel': "Nom d'utilisateur", 'profile.addressLabel': 'Adresse de dépôt', 'profile.balanceLabel': 'Solde interne', 'profile.createdLabel': 'Utilisateur depuis', 'settings.title': 'Paramètres', 'settings.hint': 'Changez le mot de passe de votre compte', 'settings.currentPasswordLabel': 'Mot de passe actuel', 'settings.newPasswordLabel': 'Nouveau mot de passe', 'settings.newPasswordConfirmLabel': 'Confirmer le nouveau mot de passe', 'settings.updateBtn': 'Mettre à jour le mot de passe', 'toast.passwordMismatch': 'Les mots de passe ne correspondent pas.', 'toast.accountCreated': 'Compte créé.', 'toast.loginSuccess': 'Connexion réussie.', 'toast.requestTimeout': 'La requête au serveur a expiré.', 'toast.addressCopied': 'Adresse copiée.', 'toast.copyFailed': 'Impossible de copier automatiquement.', 'toast.newPasswordMismatch': 'Les nouveaux mots de passe ne correspondent pas.', 'toast.passwordTooShort': 'Le nouveau mot de passe doit comporter au moins 8 caractères.', 'toast.passwordUpdated': 'Mot de passe mis à jour.', 'toast.betPlaced': 'Mise placée sur le round #{id}.', 'toast.roundWon': 'Vous avez gagné le round #{id} ! +{amount} PLM', 'toast.withdrawSent': 'Retrait envoyé.', 'error.network_unavailable': 'Pas de connexion au réseau, réessayez dans un instant.', 'error.no_round_open': "Aucun round n'est ouvert pour le moment, réessayez dans un instant.", 'error.round_closing': 'Le round en cours est en train de se fermer, réessayez dans un instant.', 'error.already_betting': 'Vous avez déjà une mise active dans le round en cours.', 'error.insufficient_balance': 'Solde insuffisant.', 'error.balance_pending_confirmation': 'Vous avez {pending_plm} PLM en attente de confirmation — pas encore disponibles.', 'error.amount_below_network_fee': 'Le montant est trop faible pour couvrir les frais de réseau.', 'error.invalid_address': 'Adresse PLM invalide (elle doit commencer par plm1q…).', 'error.amount_below_minimum': 'Le montant minimum de retrait est de {minimum_plm} PLM.', 'error.current_password_incorrect': "Le mot de passe actuel n'est pas correct.", 'error.password_too_short': 'Le nouveau mot de passe doit comporter au moins {minimum} caractères.', 'error.username_taken': "Ce nom d'utilisateur est déjà pris.", 'error.invalid_credentials': "Nom d'utilisateur ou mot de passe incorrect.", 'error.derivation_index_conflict': "L'inscription a échoué, veuillez réessayer.", 'error.session_expired': 'Session expirée, veuillez vous reconnecter.', 'error.invalid_request': 'Requête invalide, vérifiez les données saisies.', 'error.invalid_amount': 'Saisissez un montant supérieur à zéro.', 'error.broadcast_failed': 'Le réseau a refusé la transaction. Veuillez réessayer dans un instant.', 'error.amount_below_dust_limit': "Le montant est trop faible pour être envoyé.", 'error.withdrawal_to_own_address': "C'est votre propre adresse de dépôt — retirez vers un portefeuille externe.", 'error.internal_error': 'Erreur inattendue du serveur. Veuillez réessayer dans un instant.', 'error.guide_unavailable': "Le guide n'est pas disponible pour le moment.", 'error.rate_limited': 'Trop de tentatives, réessayez dans {retry_after_seconds} secondes.', 'loading.creating': 'Création…', 'loading.loggingIn': 'Connexion…', 'loading.sendingBet': 'Envoi de la mise…', 'loading.updating': 'Mise à jour…', 'loading.refreshing': '…', 'loading.sending': 'Envoi…', }, de: { 'nav.ariaSections': 'Bereiche', 'nav.guideTitle': 'Anleitung', 'nav.guideAria': 'Benutzerhandbuch öffnen', 'nav.bugReport': 'Fehler melden', 'nav.logoutTitle': 'Abmelden', 'nav.logoutAria': 'Vom Konto abmelden', 'nav.deposit': 'Einzahlung', 'nav.bet': 'Wette', 'nav.withdraw': 'Auszahlung', 'nav.profile': 'Profil', 'chain.connecting': 'Verbindung wird hergestellt…', 'chain.block': 'Block {n}', 'chain.connectionLost': 'Verbindung zum Server verloren — erneuter Versuch…', 'maintenance.banner': 'Geplante Wartung: Die laufende Runde wird regulär abgeschlossen (Gewinner inklusive), aber die nächste Runde öffnet erst, wenn die Wartung beendet ist.', 'hero.lead': 'Zahle PLM ein, nimm mit einem festen Einsatz an der Runde teil, und wenn deine Zahl gezogen wird, gewinnst du den Jackpot.', 'hero.step1.title': '1. Einzahlen', 'hero.step1.hint': 'Erhalte deine persönliche PLM-Adresse, für immer deine', 'hero.step2.title': '2. Spielen', 'hero.step2.hint': 'Ein Einsatz mit Festpreis für die Teilnahme an der laufenden Runde', 'hero.step3.title': '3. Gewinnen', 'hero.step3.hint': 'Ziehung aus einem Block-Hash, Jackpot sofort gutgeschrieben', 'trust.fixedRate': 'Fester, offen genannter Einsatz', 'trust.blockHash': 'Ziehung aus einem Block-Hash', 'trust.freeWithdraw': 'Jederzeit freie Auszahlung', 'auth.tabLogin': 'Anmelden', 'auth.tabRegister': 'Registrieren', 'auth.username': 'Benutzername', 'auth.password': 'Passwort', 'auth.passwordConfirm': 'Passwort bestätigen', 'auth.loginBtn': 'Anmelden', 'auth.registerBtn': 'Konto erstellen', 'round.players': 'Spieler', 'round.jackpot': 'Jackpot', 'round.status.open': 'offen', 'round.status.closing': 'wird geschlossen', 'round.status.drawing': 'Ziehung läuft', 'round.status.paying_out': 'Auszahlung an den Gewinner läuft', 'round.title': 'Runde #{id} — {status}', 'round.none': 'Keine aktive Runde', 'chain.status.waiting': 'Warten auf die nächste Runde', 'chain.status.open': 'Runde offen', 'chain.status.closing': 'Runde geschlossen — warte auf Bestätigung der Wetten', 'chain.status.drawing': 'Ziehung läuft', 'chain.status.paying_out': 'Auszahlung an den Gewinner läuft', 'draw.defaultLabel': 'Gewinner wird gezogen…', 'draw.closing': 'Runde geschlossen — warte auf Bestätigung der letzten Wette, bevor der Gewinner gezogen wird…', 'draw.drawing': 'Warte auf den nächsten Block, um den Gewinner zu ziehen…', 'draw.payingOutBlock': 'Gewinner aus Block #{height} gezogen — Auszahlung an den Gewinner läuft…', 'draw.payingOut': 'Gewinner gezogen — Auszahlung an den Gewinner läuft…', 'result.win': '🎉 Du hast gewonnen! +{amount} PLM', 'result.lose': 'Diesmal kein Gewinn.', 'deposit.balanceTitle': 'Internes Guthaben', 'deposit.balanceHint': 'Aktualisiert nach 1 Netzwerkbestätigung', 'deposit.refreshBtn': 'Aktualisieren', 'deposit.refreshAria': 'Guthaben aktualisieren', 'deposit.addressTitle': 'Einzahlungsadresse', 'deposit.addressHint': 'Dies ist auch die Adresse, auf der du eventuelle Gewinne erhältst', 'deposit.copyAria': 'Adresse kopieren', 'deposit.qrAlt': 'QR-Code der Einzahlungsadresse', 'bet.title': 'Wette', 'bet.hint': 'Fester Einsatz für die laufende Runde', 'bet.button': 'Wette platzieren ({amount} PLM)', 'bet.buttonNoAmount': 'Wette platzieren', 'withdraw.title': 'Auszahlung', 'withdraw.hint': 'Sende Guthaben an eine externe PLM-Adresse', 'withdraw.addressLabel': 'Externe Adresse', 'withdraw.addressHint': 'Nur P2WPKH-Bech32-Adressen (beginnend mit plm1q...). Legacy-Adressen (P...) oder P2SH werden nicht unterstützt.', 'withdraw.amountLabel': 'Betrag (PLM)', 'withdraw.amountPlaceholder': 'z. B. 2', 'withdraw.fullAmountPrefix': 'Gesamten Betrag auszahlen (', 'withdraw.fullAmountSuffix': ' PLM)', 'withdraw.button': 'Auszahlen', 'profile.title': 'Profil', 'profile.hint': 'Deine Kontoinformationen', 'profile.usernameLabel': 'Benutzername', 'profile.addressLabel': 'Einzahlungsadresse', 'profile.balanceLabel': 'Internes Guthaben', 'profile.createdLabel': 'Nutzer seit', 'settings.title': 'Einstellungen', 'settings.hint': 'Ändere das Passwort deines Kontos', 'settings.currentPasswordLabel': 'Aktuelles Passwort', 'settings.newPasswordLabel': 'Neues Passwort', 'settings.newPasswordConfirmLabel': 'Neues Passwort bestätigen', 'settings.updateBtn': 'Passwort aktualisieren', 'toast.passwordMismatch': 'Die Passwörter stimmen nicht überein.', 'toast.accountCreated': 'Konto erstellt.', 'toast.loginSuccess': 'Anmeldung erfolgreich.', 'toast.requestTimeout': 'Anfrage an den Server ist abgelaufen.', 'toast.addressCopied': 'Adresse kopiert.', 'toast.copyFailed': 'Automatisches Kopieren fehlgeschlagen.', 'toast.newPasswordMismatch': 'Die neuen Passwörter stimmen nicht überein.', 'toast.passwordTooShort': 'Das neue Passwort muss mindestens 8 Zeichen lang sein.', 'toast.passwordUpdated': 'Passwort aktualisiert.', 'toast.betPlaced': 'Wette auf Runde #{id} platziert.', 'toast.roundWon': 'Du hast Runde #{id} gewonnen! +{amount} PLM', 'toast.withdrawSent': 'Auszahlung gesendet.', 'error.network_unavailable': 'Keine Verbindung zum Netzwerk, bitte versuche es gleich erneut.', 'error.no_round_open': 'Derzeit ist keine Runde offen, bitte versuche es gleich erneut.', 'error.round_closing': 'Die laufende Runde wird gerade geschlossen, bitte versuche es gleich erneut.', 'error.already_betting': 'Du hast bereits eine aktive Wette in der laufenden Runde.', 'error.insufficient_balance': 'Nicht genügend Guthaben.', 'error.balance_pending_confirmation': 'Sie haben {pending_plm} PLM, die noch auf Bestätigung warten — noch nicht verfügbar.', 'error.amount_below_network_fee': 'Der Betrag ist zu klein, um die Netzwerkgebühr zu decken.', 'error.invalid_address': 'Keine gültige PLM-Adresse (sie muss mit plm1q… beginnen).', 'error.amount_below_minimum': 'Der Mindestauszahlungsbetrag beträgt {minimum_plm} PLM.', 'error.current_password_incorrect': 'Das aktuelle Passwort ist nicht korrekt.', 'error.password_too_short': 'Das neue Passwort muss mindestens {minimum} Zeichen lang sein.', 'error.username_taken': 'Dieser Benutzername ist bereits vergeben.', 'error.invalid_credentials': 'Benutzername oder Passwort falsch.', 'error.derivation_index_conflict': 'Registrierung fehlgeschlagen, bitte erneut versuchen.', 'error.session_expired': 'Sitzung abgelaufen, bitte melde dich erneut an.', 'error.invalid_request': 'Ungültige Anfrage, bitte überprüfe die eingegebenen Daten.', 'error.invalid_amount': 'Gib einen Betrag größer als null ein.', 'error.broadcast_failed': 'Das Netzwerk hat die Transaktion abgelehnt. Bitte versuche es in Kürze erneut.', 'error.amount_below_dust_limit': 'Der Betrag ist zu klein, um gesendet zu werden.', 'error.withdrawal_to_own_address': 'Das ist deine eigene Einzahlungsadresse — zahle auf eine externe Wallet aus.', 'error.internal_error': 'Unerwarteter Serverfehler. Bitte versuche es in Kürze erneut.', 'error.guide_unavailable': 'Die Anleitung ist derzeit nicht verfügbar.', 'error.rate_limited': 'Zu viele Versuche, bitte versuche es in {retry_after_seconds} Sekunden erneut.', 'loading.creating': 'Wird erstellt…', 'loading.loggingIn': 'Anmeldung…', 'loading.sendingBet': 'Wette wird gesendet…', 'loading.updating': 'Aktualisierung…', 'loading.refreshing': '…', 'loading.sending': 'Wird gesendet…', }, ru: { 'nav.ariaSections': 'Разделы', 'nav.guideTitle': 'Инструкция', 'nav.guideAria': 'Открыть руководство пользователя', 'nav.bugReport': 'Сообщить об ошибке', 'nav.logoutTitle': 'Выйти', 'nav.logoutAria': 'Выйти из аккаунта', 'nav.deposit': 'Депозит', 'nav.bet': 'Ставка', 'nav.withdraw': 'Вывод', 'nav.profile': 'Профиль', 'chain.connecting': 'Подключение…', 'chain.block': 'Блок {n}', 'chain.connectionLost': 'Соединение с сервером потеряно — повторная попытка…', 'maintenance.banner': 'Запланировано техобслуживание: текущий раунд завершится в обычном порядке (включая победителя), но следующий раунд не откроется до окончания техобслуживания.', 'hero.lead': 'Внесите PLM, вступите в раунд с фиксированной ставкой, и если выпадет ваш номер — вы выиграете джекпот.', 'hero.step1.title': '1. Внесите депозит', 'hero.step1.hint': 'Получите личный адрес PLM, ваш навсегда', 'hero.step2.title': '2. Играйте', 'hero.step2.hint': 'Ставка с фиксированной стоимостью для участия в текущем раунде', 'hero.step3.title': '3. Выигрывайте', 'hero.step3.hint': 'Розыгрыш по хешу блока, джекпот начисляется сразу', 'trust.fixedRate': 'Заявленная фиксированная ставка', 'trust.blockHash': 'Розыгрыш по хешу блока', 'trust.freeWithdraw': 'Свободный вывод средств в любой момент', 'auth.tabLogin': 'Вход', 'auth.tabRegister': 'Регистрация', 'auth.username': 'Имя пользователя', 'auth.password': 'Пароль', 'auth.passwordConfirm': 'Подтвердите пароль', 'auth.loginBtn': 'Войти', 'auth.registerBtn': 'Создать аккаунт', 'round.players': 'Игроки', 'round.jackpot': 'Джекпот', 'round.status.open': 'открыт', 'round.status.closing': 'закрывается', 'round.status.drawing': 'идёт розыгрыш', 'round.status.paying_out': 'выплата победителю', 'round.title': 'Раунд #{id} — {status}', 'round.none': 'Нет активного раунда', 'chain.status.waiting': 'Ожидание следующего раунда', 'chain.status.open': 'Раунд открыт', 'chain.status.closing': 'Раунд закрыт — ожидание подтверждения ставок', 'chain.status.drawing': 'Идёт розыгрыш', 'chain.status.paying_out': 'Выплата победителю', 'draw.defaultLabel': 'Розыгрыш победителя…', 'draw.closing': 'Раунд закрыт — ожидание подтверждения последней ставки перед розыгрышем победителя…', 'draw.drawing': 'Ожидание следующего блока для розыгрыша победителя…', 'draw.payingOutBlock': 'Победитель определён по блоку #{height} — выплата победителю…', 'draw.payingOut': 'Победитель определён — выплата победителю…', 'result.win': '🎉 Вы выиграли! +{amount} PLM', 'result.lose': 'На этот раз без выигрыша.', 'deposit.balanceTitle': 'Внутренний баланс', 'deposit.balanceHint': 'Обновляется после 1 подтверждения в сети', 'deposit.refreshBtn': 'Обновить', 'deposit.refreshAria': 'Обновить баланс', 'deposit.addressTitle': 'Адрес для депозита', 'deposit.addressHint': 'Это также адрес, на который вы получаете возможные выигрыши', 'deposit.copyAria': 'Скопировать адрес', 'deposit.qrAlt': 'QR-код адреса для депозита', 'bet.title': 'Ставка', 'bet.hint': 'Фиксированный вход в текущий раунд', 'bet.button': 'Сделать ставку ({amount} PLM)', 'bet.buttonNoAmount': 'Сделать ставку', 'withdraw.title': 'Вывод средств', 'withdraw.hint': 'Отправьте средства на внешний адрес PLM', 'withdraw.addressLabel': 'Внешний адрес', 'withdraw.addressHint': 'Только адреса P2WPKH bech32 (начинающиеся с plm1q...). Устаревшие адреса (P...) или P2SH не поддерживаются.', 'withdraw.amountLabel': 'Сумма (PLM)', 'withdraw.amountPlaceholder': 'напр. 2', 'withdraw.fullAmountPrefix': 'Вывести всю сумму (', 'withdraw.fullAmountSuffix': ' PLM)', 'withdraw.button': 'Вывести', 'profile.title': 'Профиль', 'profile.hint': 'Информация о вашем аккаунте', 'profile.usernameLabel': 'Имя пользователя', 'profile.addressLabel': 'Адрес для депозита', 'profile.balanceLabel': 'Внутренний баланс', 'profile.createdLabel': 'Пользователь с', 'settings.title': 'Настройки', 'settings.hint': 'Измените пароль вашего аккаунта', 'settings.currentPasswordLabel': 'Текущий пароль', 'settings.newPasswordLabel': 'Новый пароль', 'settings.newPasswordConfirmLabel': 'Подтвердите новый пароль', 'settings.updateBtn': 'Обновить пароль', 'toast.passwordMismatch': 'Пароли не совпадают.', 'toast.accountCreated': 'Аккаунт создан.', 'toast.loginSuccess': 'Вход выполнен успешно.', 'toast.requestTimeout': 'Истекло время ожидания ответа сервера.', 'toast.addressCopied': 'Адрес скопирован.', 'toast.copyFailed': 'Не удалось скопировать автоматически.', 'toast.newPasswordMismatch': 'Новые пароли не совпадают.', 'toast.passwordTooShort': 'Новый пароль должен содержать не менее 8 символов.', 'toast.passwordUpdated': 'Пароль обновлён.', 'toast.betPlaced': 'Ставка сделана на раунд #{id}.', 'toast.roundWon': 'Вы выиграли раунд #{id}! +{amount} PLM', 'toast.withdrawSent': 'Вывод средств отправлен.', 'error.network_unavailable': 'Нет соединения с сетью, повторите попытку чуть позже.', 'error.no_round_open': 'Сейчас нет открытого раунда, повторите попытку чуть позже.', 'error.round_closing': 'Текущий раунд закрывается, повторите попытку чуть позже.', 'error.already_betting': 'У вас уже есть активная ставка в текущем раунде.', 'error.insufficient_balance': 'Недостаточно средств.', 'error.balance_pending_confirmation': 'У вас есть {pending_plm} PLM, ожидающих подтверждения — они пока недоступны для расходования.', 'error.amount_below_network_fee': 'Сумма слишком мала, чтобы покрыть комиссию сети.', 'error.invalid_address': 'Некорректный адрес PLM (он должен начинаться с plm1q…).', 'error.amount_below_minimum': 'Минимальная сумма вывода — {minimum_plm} PLM.', 'error.current_password_incorrect': 'Текущий пароль указан неверно.', 'error.password_too_short': 'Новый пароль должен содержать не менее {minimum} символов.', 'error.username_taken': 'Это имя пользователя уже занято.', 'error.invalid_credentials': 'Неверное имя пользователя или пароль.', 'error.derivation_index_conflict': 'Не удалось завершить регистрацию, попробуйте ещё раз.', 'error.session_expired': 'Сессия истекла, войдите снова.', 'error.invalid_request': 'Некорректный запрос, проверьте введённые данные.', 'error.invalid_amount': 'Введите сумму больше нуля.', 'error.broadcast_failed': 'Сеть отклонила транзакцию. Попробуйте ещё раз через минуту.', 'error.amount_below_dust_limit': 'Сумма слишком мала для отправки.', 'error.withdrawal_to_own_address': 'Это ваш собственный адрес для депозита — выводите на внешний кошелёк.', 'error.internal_error': 'Непредвиденная ошибка сервера. Попробуйте ещё раз через минуту.', 'error.guide_unavailable': 'Руководство сейчас недоступно.', 'error.rate_limited': 'Слишком много попыток, повторите через {retry_after_seconds} сек.', 'loading.creating': 'Создание…', 'loading.loggingIn': 'Вход…', 'loading.sendingBet': 'Отправка ставки…', 'loading.updating': 'Обновление…', 'loading.refreshing': '…', 'loading.sending': 'Отправка…', }, zh: { 'nav.ariaSections': '导航区', 'nav.guideTitle': '指南', 'nav.guideAria': '打开用户指南', 'nav.bugReport': '报告问题', 'nav.logoutTitle': '退出登录', 'nav.logoutAria': '退出账户', 'nav.deposit': '存款', 'nav.bet': '下注', 'nav.withdraw': '提现', 'nav.profile': '个人资料', 'chain.connecting': '正在连接…', 'chain.block': '区块 {n}', 'chain.connectionLost': '与服务器的连接已断开——正在重试…', 'maintenance.banner': '计划维护:当前回合将照常完成(包括中奖者),但下一回合要等维护结束后才会开启。', 'hero.lead': '存入 PLM,以固定金额参与本回合,若抽中你的号码即可赢得奖池。', 'hero.step1.title': '1. 存款', 'hero.step1.hint': '获得专属于你的 PLM 地址,永久有效', 'hero.step2.title': '2. 参与', 'hero.step2.hint': '以固定金额下注即可参与当前回合', 'hero.step3.title': '3. 获胜', 'hero.step3.hint': '根据区块哈希开奖,奖金即时到账', 'trust.fixedRate': '公开声明的固定下注金额', 'trust.blockHash': '根据区块哈希开奖', 'trust.freeWithdraw': '随时自由提现', 'auth.tabLogin': '登录', 'auth.tabRegister': '注册', 'auth.username': '用户名', 'auth.password': '密码', 'auth.passwordConfirm': '确认密码', 'auth.loginBtn': '登录', 'auth.registerBtn': '创建账户', 'round.players': '参与人数', 'round.jackpot': '奖池', 'round.status.open': '进行中', 'round.status.closing': '即将结束', 'round.status.drawing': '正在开奖', 'round.status.paying_out': '正在向中奖者付款', 'round.title': '第 {id} 回合 — {status}', 'round.none': '当前没有进行中的回合', 'chain.status.waiting': '等待下一回合开启', 'chain.status.open': '回合进行中', 'chain.status.closing': '回合已结束——等待下注确认', 'chain.status.drawing': '正在开奖', 'chain.status.paying_out': '正在向中奖者付款', 'draw.defaultLabel': '正在抽取中奖者…', 'draw.closing': '回合已结束——在开奖前等待最后一笔下注确认…', 'draw.drawing': '等待下一个区块以抽取中奖者…', 'draw.payingOutBlock': '已从区块 #{height} 抽取中奖者——正在向中奖者付款…', 'draw.payingOut': '中奖者已确定——正在向中奖者付款…', 'result.win': '🎉 恭喜你赢了!+{amount} PLM', 'result.lose': '这次没有中奖。', 'deposit.balanceTitle': '内部余额', 'deposit.balanceHint': '在网络确认 1 次后更新', 'deposit.refreshBtn': '刷新', 'deposit.refreshAria': '刷新余额', 'deposit.addressTitle': '存款地址', 'deposit.addressHint': '这也是接收任何奖金的地址', 'deposit.copyAria': '复制地址', 'deposit.qrAlt': '存款地址的二维码', 'bet.title': '下注', 'bet.hint': '以固定金额参与当前回合', 'bet.button': '下注({amount} PLM)', 'bet.buttonNoAmount': '下注', 'withdraw.title': '提现', 'withdraw.hint': '将资金发送到外部 PLM 地址', 'withdraw.addressLabel': '外部地址', 'withdraw.addressHint': '仅支持 P2WPKH bech32 地址(以 plm1q... 开头)。不支持传统地址(P...)或 P2SH 地址。', 'withdraw.amountLabel': '金额(PLM)', 'withdraw.amountPlaceholder': '例如 2', 'withdraw.fullAmountPrefix': '提取全部金额(', 'withdraw.fullAmountSuffix': ' PLM)', 'withdraw.button': '提现', 'profile.title': '个人资料', 'profile.hint': '你的账户信息', 'profile.usernameLabel': '用户名', 'profile.addressLabel': '存款地址', 'profile.balanceLabel': '内部余额', 'profile.createdLabel': '注册时间', 'settings.title': '设置', 'settings.hint': '修改你的账户密码', 'settings.currentPasswordLabel': '当前密码', 'settings.newPasswordLabel': '新密码', 'settings.newPasswordConfirmLabel': '确认新密码', 'settings.updateBtn': '更新密码', 'toast.passwordMismatch': '两次输入的密码不一致。', 'toast.accountCreated': '账户已创建。', 'toast.loginSuccess': '登录成功。', 'toast.requestTimeout': '服务器请求超时。', 'toast.addressCopied': '地址已复制。', 'toast.copyFailed': '自动复制失败。', 'toast.newPasswordMismatch': '两次输入的新密码不一致。', 'toast.passwordTooShort': '新密码长度至少需要 8 个字符。', 'toast.passwordUpdated': '密码已更新。', 'toast.betPlaced': '已在第 {id} 回合下注。', 'toast.roundWon': '你赢得了第 {id} 回合!+{amount} PLM', 'toast.withdrawSent': '提现已发送。', 'error.network_unavailable': '未连接到网络,请稍后重试。', 'error.no_round_open': '当前没有开放的回合,请稍后重试。', 'error.round_closing': '当前回合正在结束,请稍后重试。', 'error.already_betting': '你在当前回合已有一笔有效下注。', 'error.insufficient_balance': '余额不足。', 'error.balance_pending_confirmation': '您有 {pending_plm} PLM 待确认 —— 尚不可用于支出。', 'error.amount_below_network_fee': '金额太小,不足以支付网络手续费。', 'error.invalid_address': 'PLM 地址无效(必须以 plm1q… 开头)。', 'error.amount_below_minimum': '最低提现金额为 {minimum_plm} PLM。', 'error.current_password_incorrect': '当前密码不正确。', 'error.password_too_short': '新密码长度至少需要 {minimum} 个字符。', 'error.username_taken': '该用户名已被占用。', 'error.invalid_credentials': '用户名或密码错误。', 'error.derivation_index_conflict': '注册失败,请重试。', 'error.session_expired': '会话已过期,请重新登录。', 'error.invalid_request': '请求无效,请检查填写的内容。', 'error.invalid_amount': '请输入大于零的金额。', 'error.broadcast_failed': '网络拒绝了该交易,请稍后重试。', 'error.amount_below_dust_limit': '金额过小,无法发送。', 'error.withdrawal_to_own_address': '这是你自己的充值地址 — 请提现到外部钱包。', 'error.internal_error': '服务器发生意外错误,请稍后重试。', 'error.guide_unavailable': '指南当前不可用。', 'error.rate_limited': '尝试次数过多,请在 {retry_after_seconds} 秒后重试。', 'loading.creating': '正在创建…', 'loading.loggingIn': '正在登录…', 'loading.sendingBet': '正在下注…', 'loading.updating': '正在更新…', 'loading.refreshing': '…', 'loading.sending': '正在发送…', }, }; function detectDefaultLang() { const saved = localStorage.getItem(LANG_STORAGE_KEY); if (saved && SUPPORTED_LANGS.includes(saved)) return saved; const nav = (navigator.language || 'en').slice(0, 2).toLowerCase(); return SUPPORTED_LANGS.includes(nav) ? nav : 'en'; } let currentLang = detectDefaultLang(); function interpolate(str, params) { if (!params) return str; for (const [k, v] of Object.entries(params)) str = str.replaceAll('{' + k + '}', v); return str; } function t(key, params) { const dict = TRANSLATIONS[currentLang] || TRANSLATIONS.en; return interpolate(dict[key] ?? TRANSLATIONS.en[key] ?? key, params); } // Like t(), but returns null instead of echoing the key back when nothing is // defined for it — lets a caller fall back to a string of its own (e.g. the // API's English message for an error code this build doesn't know yet). function tOrNull(key, params) { const str = TRANSLATIONS[currentLang]?.[key] ?? TRANSLATIONS.en[key]; return str === undefined ? null : interpolate(str, params); } function currentDateLocale() { return DATE_LOCALES[currentLang] || 'en-US'; } // `root` defaults to the whole document; pass a subtree to re-translate just // that part (see withLoading in app.js, which restores a button's markup from a // snapshot that may predate a language change). function applyStaticTranslations(root) { const scope = root instanceof Element || root instanceof Document ? root : document; const each = (selector, fn) => { if (scope !== document && scope.matches?.(selector)) fn(scope); scope.querySelectorAll(selector).forEach(fn); }; each('[data-i18n]', (el) => { el.textContent = t(el.getAttribute('data-i18n')); }); each('[data-i18n-html]', (el) => { el.innerHTML = t(el.getAttribute('data-i18n-html')); }); each('[data-i18n-placeholder]', (el) => { el.placeholder = t(el.getAttribute('data-i18n-placeholder')); }); each('[data-i18n-title]', (el) => { el.title = t(el.getAttribute('data-i18n-title')); }); each('[data-i18n-aria-label]', (el) => { el.setAttribute('aria-label', t(el.getAttribute('data-i18n-aria-label'))); }); each('[data-i18n-alt]', (el) => { el.alt = t(el.getAttribute('data-i18n-alt')); }); if (scope !== document) return; document.documentElement.lang = currentLang; const switcher = document.getElementById('lang-switcher'); if (switcher) switcher.value = currentLang; } function setLanguage(lang) { if (!SUPPORTED_LANGS.includes(lang) || lang === currentLang) return; currentLang = lang; localStorage.setItem(LANG_STORAGE_KEY, lang); applyStaticTranslations(); if (typeof onLanguageChange === 'function') onLanguageChange(); } document.addEventListener('DOMContentLoaded', applyStaticTranslations);