{ "passphrase": "Cụm mật khẩu", "howItWorks": "Cách hoạt động", "installApp": "Cài đặt ứng dụng", "hiwBasicsTitle": "Những điều cơ bản", "signExport": "Ký & (phát đi nơi khác)", "scanQr": "Quét QR", "scanHint": "Hướng camera vào mã QR", "scanNoCamera": "Không thể truy cập camera.", "scanNoDecoder": "Quét QR cần kết nối trong lần sử dụng đầu tiên.", "scanUnrecognized": "Không thể đọc địa chỉ Bitcoin từ mã QR đó.", "broadcastScanned": "Phát giao dịch?", "broadcastNow": "Phát đi", "scanOffline": "Bạn đang ngoại tuyến, hãy kết nối lại để phát giao dịch này.", "loadingHistory": "Đang tải giao dịch…", "explorer": "Trình duyệt khối", "explorerDesc": "Máy chủ mà ví này truy vấn để lấy số dư và lịch sử. Hãy trỏ nó tới node của riêng bạn để có quyền riêng tư tối đa.", "explorerUrl": "URL máy chủ", "explorerUrlHint": "Cơ sở REST của Esplora / electrs, ví dụ https://mempool.space/api", "home": "Trang chủ", "createNew": "Tạo mới", "importExisting": "Nhập ví có sẵn", "genIntro": "Tạo cụm từ hạt giống 12 từ mới. Hãy ghi lại. Đây là cách duy nhất để khôi phục ví này.", "generateSeed": "Tạo cụm từ hạt giống", "writeDownWarn": "⚠ Ghi 12 từ này ra giấy, theo đúng thứ tự. Bất kỳ ai có chúng đều có thể tiêu coin của bạn.", "copyPhrase": "Sao chép cụm từ", "regenerate": "Tạo lại", "verifyBackup": "Xác minh sao lưu", "skipVerification": "Bỏ qua xác minh", "confirmBackupIntro": "Xác nhận bản sao lưu bằng cách nhập các từ được yêu cầu.", "wordN": "Từ #{n}", "reenterPassphrase": "Nhập lại mật khẩu", "back": "← Quay lại", "openWallet": "Mở ví", "wordsMismatch": "Các từ này không khớp với cụm từ của bạn. Hãy kiểm tra bản sao lưu.", "passphraseMismatch": "Mật khẩu không khớp với mật khẩu bạn đã nhập.", "seedPhrase": "Cụm từ hạt giống", "seedPlaceholder": "nhập các từ cách nhau bằng dấu cách", "optionalPassphrase": "Mật khẩu tùy chọn", "leaveBlank": "để trống nếu không dùng", "show": "Hiện", "hide": "Ẩn", "invalidSeed": "Cụm từ hạt giống không hợp lệ, kiểm tra chính tả và thứ tự.", "keysNote": "Khóa không bao giờ rời khỏi trình duyệt của bạn. Hoạt động hoàn toàn ngoại tuyến. Lưu trang này và mở cục bộ.", "balance": "Số dư", "pending": "Đang chờ", "tabReceive": "Nhận", "tabSend": "Gửi", "tabHistory": "Lịch sử", "tabSettings": "Cài đặt", "copyAddress": "Sao chép địa chỉ", "paymentReceived": "Đã nhận thanh toán!", "tapToProceed": "Chạm vào bất kỳ đâu để tiếp tục", "recipient": "Người nhận", "recipients": "Người nhận", "max": "Tối đa", "feeRate": "Phí", "feeEconomy": "Tiết kiệm", "feeNormal": "Thường", "feePriority": "Ưu tiên", "feeCustom": "Tùy chỉnh", "selectedRate": "Phí đã chọn: {n} sat/vB", "coinSelection": "Chọn coin", "automatic": "Tự động", "manual": "Thủ công", "noCoins": "Không có coin khả dụng.", "nSelected": "Đã chọn {n}", "addRecipient": "+ Thêm người nhận", "reviewTx": "Xem lại giao dịch", "remove": "Xóa", "enterRecipientAddr": "Nhập địa chỉ người nhận.", "enterAddrForN": "Nhập địa chỉ cho người nhận {n}.", "enterValidAmtForN": "Nhập số tiền hợp lệ cho người nhận {n}.", "selectCoin": "Chọn ít nhất một coin để chi.", "networkFee": "Phí mạng", "offlineSignNote": "Ngoại tuyến: ký ngay, sau đó phát giao dịch từ thiết bị có mạng.", "signBroadcast": "Ký & phát", "signTx": "Ký giao dịch", "broadcastFailed": "Phát thất bại: {msg}", "signingFailed": "Ký thất bại: {msg}", "done": "Xong", "txSignedNote": "✓ Đã ký giao dịch. Phát mã bên dưới từ thiết bị có mạng (vd. mempool.space → Broadcast).", "transactionId": "ID giao dịch", "signedTxRaw": "Giao dịch đã ký (mã)", "copyHex": "Sao chép mã", "downloadLabel": "Tải xuống", "showQrAirgap": "Hiện QR (chuyển ngoại tuyến)", "txBroadcast": "✓ Đã phát giao dịch!", "copyTxid": "Sao chép ID giao dịch", "viewOnMempool": "Xem trên block explorer ↗", "noTxYet": "Chưa có giao dịch.", "received": "Đã nhận", "sent": "Đã gửi", "pendingTag": "⏳ đang chờ", "awaitingConfirmation": "đang chờ xác nhận", "feeShort": "phí {x}", "historyOffline": "Không xem được lịch sử khi ngoại tuyến.", "status": "Trạng thái", "confirmed": "Đã xác nhận", "confirmedTag": "đã xác nhận", "pendingInMempool": "Đang chờ (trong mempool)", "block": "Khối", "date": "Ngày", "copyId": "Sao chép ID", "backToHistory": "← Về lịch sử", "recoveryPhrase": "Cụm từ khôi phục", "recoveryWarn": "⚠ Bất kỳ ai thấy các từ này đều có thể đánh cắp tiền của bạn. Hãy chắc chắn không ai nhìn trước khi hiển thị.", "bip39Passphrase": "Mật khẩu", "copyPassphrase": "Sao chép mật khẩu", "revealRecovery": "Hiện cụm từ khôi phục", "offlineTransfer": "Chuyển ngoại tuyến", "offlineTransferDesc": "Xuất coin trên thiết bị có mạng, rồi nhập tệp trên thiết bị ngoại tuyến (có ví và hạt giống này) để ký không cần internet.", "exportSnapshot": "⤓ Xuất snapshot", "importSnapshot": "Nhập snapshot…", "rescan": "Quét lại", "rescanDesc": "Địa chỉ hiện tại tự cập nhật. Chỉ quét lại nếu thanh toán được gửi đến địa chỉ cũ (dùng lại) và không hiển thị.", "rescanWallet": "Quét lại ví", "scanning": "Đang quét…", "language": "Ngôn ngữ", "retry": "Thử lại", "copied": "Đã sao chép", "snapshotExported": "Đã xuất snapshot", "importedNCoins": "Đã nhập {n} coin", "unmatchedSuffix": " · {n} địa chỉ không thuộc ví này", "importFailed": "Nhập thất bại: {msg}", "copy": "Sao chép", "switchUnit": "Đổi BTC / sats", "footerMadeBy": "làm bởi", "footerSourceOn": "mã nguồn tại", "bumpFee": "Tăng phí", "bumpConfirm": "Thay thế với phí cao hơn?", "replaceTx": "Thay thế", "bumpInsufficient": "Không đủ tiền để tăng ở mức phí này. Hãy thử mức thấp hơn.", "giftWelcome": "Bạn đã được gửi Bitcoin", "giftWelcomeBody": "Ví này giờ là của bạn. Bất kỳ ai có cụm từ khôi phục đều kiểm soát số coin, vì vậy hãy sao lưu trước khi làm bất kỳ điều gì khác.", "giftBackup": "Sao lưu ví này", "giftSkip": "Bỏ qua lúc này", "giftBackedUp": "Tôi đã ghi lại rồi", "giftLink": "Tặng một ít Bitcoin", "giftLinkDesc": "Ký trước một số tiền thành một liên kết. Ai mở liên kết sẽ nhận số Bitcoin đó vào một ví hoàn toàn mới mà chỉ riêng họ kiểm soát.", "giftLinkWarn": "⚠ Bất kỳ ai mở liên kết này đều có thể nhận số tiền, vì vậy chỉ chia sẻ với người bạn muốn tặng. Hãy giữ số tiền nhỏ.", "giftAmountLabel": "Số tiền muốn tặng", "giftAmountInvalid": "Nhập tối thiểu {n}.", "giftAnother": "Tạo cái khác", "giftReserved": "Liên kết quà chưa nhận ({n})", "claimBody": "Nhận nó vào một ví hoàn toàn mới mà chỉ riêng bạn kiểm soát.", "claimBtn": "Nhận ngay", "claimFailed": "Món quà này đã được nhận hoặc không còn khả dụng.", "claimedTitle": "Đã nhận!", "claimedBody": "Bitcoin của bạn đang trên đường tới. Hãy sao lưu cụm từ khôi phục để giữ an toàn.", "g
import { get } from 'svelte/store';
import { rate, user, token, invoices, newPayment } from '$lib/store';
import { success } from '$lib/utils';
const socketUrl = import.meta.env.VITE_SOCKET;
let interval, socket;
export const auth = () => {
let t = get(token);
if (t) send('login', t);
};
export const send = (type, data) => {
socket?.readyState === 1 && socket.send(JSON.stringify({ type, data }));
};
export const messages = (data) => ({
rate() {
rate.set(data);
},
payment() {
if (data.invoice) invoices.set({ ...get(invoices), [data.invoice.uuid]: data.invoice });
newPayment.set(true);
if (data.amount > 0) {
success(`Received ${data.amount} sats!`);
} else {
success(`Sent ${-data.amount} sats!`);
}
},
connected: auth,
login() {
user.set(data);
}
});
const initialReconnectDelay = 1000;
const maxReconnectDelay = 16000;
let currentReconnectDelay = initialReconnectDelay;
export function connect() {
clearInterval(interval);
if (socket) return auth();
socket = new WebSocket(socketUrl);
socket.addEventListener('open', onWebsocketOpen);
socket.addEventListener('close', onWebsocketClose);
socket.addEventListener('message', onWebsocketMessage);
interval = setInterval(() => send('heartbeat'), 5000);
}
export function close() {
if (socket) socket.close();
}
function onWebsocketMessage(msg) {
let { type, data } = JSON.parse(msg.data);
messages(data)[type] && messages(data)[type]();
}
function onWebsocketOpen() {
currentReconnectDelay = initialReconnectDelay;
}
function onWebsocketClose() {
socket = null;
setTimeout(() => {
reconnectToWebsocket();
}, currentReconnectDelay + Math.floor(Math.random() * 3000));
}
function reconnectToWebsocket() {
if (currentReconnectDelay < maxReconnectDelay) {
currentReconnectDelay *= 2;
}
connect();
}