// Lightweight i18n. t('key', {vars}) interpolates {var} and falls back to the // English string (then the key). The chosen language persists in localStorage; // otherwise it's guessed from the browser. Arabic/Urdu render right-to-left. // // NOTE: non-English translations are machine-generated and would benefit from // native-speaker review. The English table (en) is the source of truth. export const LANGS = [ ['en', 'English'], ['zh', '中文'], ['hi', 'हिन्दी'], ['es', 'Español'], ['fr', 'Français'], ['ar', 'العربية'], ['bn', 'বাংলা'], ['pt', 'Português'], ['ru', 'Русский'], ['ur', 'اردو'], ['id', 'Indonesia'], ['de', 'Deutsch'], ['ja', '日本語'], ['sw', 'Kiswahili'], ['mr', 'मराठी'], ['te', 'తెలుగు'], ['tr', 'Türkçe'], ['ta', 'தமிழ்'], ['vi', 'Tiếng Việt'], ['ko', '한국어'], ]; const RTL = new Set(['ar', 'ur']); export const isRTL = (l) => RTL.has(l || lang); const STR = { en: { arkHistoryIncomplete: 'History incomplete', arkDetailsNotSynced: 'Payment details not synced', arkHistoryIncompleteHelp: 'These coins have been used, but the payment details and any remaining change have not synced to this device. Open the wallet that made the payments and let it sync.', arkOriginalCoins: 'Original coin total', arkDetectedAt: 'Detected on this device', appTitle: 'coinos', home: 'Home', lockWallet: 'Lock wallet', accounts: 'Accounts', addWallet: 'Add account', signInAnother: 'Sign into another account', signInAnotherNote: 'Add a different identity (Nostr, passkey or Google) without signing out — switch between them here anytime.', switchIdentityTitle: 'Switch account', switchIdentityDesc: 'Accounts already signed in on this device. Tap one to switch — nothing is logged out.', signInNew: 'Sign into a new account', identityNoKey: 'Not opened on this device yet', clearAll: 'Delete all', logoutForget: 'Logout & forget all data', entropyToggle: 'Entropy', entropySubmit: 'Submit', entropyHint: 'The seed comes from exactly what you type. Entering the same text again recreates the same wallet, on any device.', entropyWarn: '⚠ Anyone who can guess this text can take your money. Use plenty of real-world randomness — 100+ dice rolls, or the order of a fully shuffled deck of cards.', entropyPlaceholder: '4 2 6 6 1 3 5 2 …', logoutPopBlurb: 'Your accounts stay saved on this device.', logoutOne: 'Log out of {name}', logoutOneBlurbLogin: 'Only this account leaves — the others stay signed in. You can sign back in with the same key anytime.', logoutOneBlurbSeed: 'Only this account leaves — the others stay signed in. It is removed from this device, so make sure you have its recovery phrase.', logoutAll: 'Log out of all accounts', clearAllWarn: '⚠ This removes every account from this device. Any account whose recovery phrase or key you have not backed up will be lost for good.', removeWalletTitle: 'Remove account', removeWalletWarn: '⚠ Remove “{name}” from this device? It will be gone from this browser — make sure you have its recovery phrase. Other accounts aren’t affected.', rename: 'Rename', remove: 'Remove', watchOnly: 'Watch-only', watchOnlyTag: 'watch-only', watchOnlyAdd: 'Add watch-only', watchOnlyDesc: 'Watch any wallet from its account public key. You can receive and view balance and history, but not send.', watchOnlyNote: 'This is a watch-only wallet. You can receive and view, but you cannot send.', loadSeedTitle: 'Load recovery phrase', loadSeedDesc: "Enter this wallet's recovery phrase (or private key) to spend from it — it must match this wallet.", loadSeedBtn: 'Load recovery phrase', seedPlaceholder: 'Recovery phrase or xprv', bip39PassphraseOpt: 'BIP39 passphrase (optional)', alsoSaveDevice: 'Also save to this device', enterSeedToSpend: 'Enter your recovery phrase.', seedMismatch: "That recovery phrase doesn't match this wallet.", seedNeedsPrivate: "That's a public key — enter the recovery phrase or private key to spend.", seedInvalid: 'Not a valid recovery phrase or key.', watchOnlySendTitle: 'Load your seed to send', watchOnlySendDesc: "You're viewing this wallet without its seed, so you can't spend yet. Load your recovery phrase to send.", watchXpubLabel: 'Account xpub or zpub', labelOptional: 'Label (optional)', walletLabelN: 'Account {n}', watchLabelN: 'Watch {n}', saveDevice: 'Save on this device', forgetDevice: 'Saved · forget', savedTag: 'saved', setPassword: 'Set a password (optional)', setPasswordDesc: 'Encrypts this account on this device — leave it blank to save without a password. You will need it to unlock saved accounts later.', passwordOptional: 'Password (optional)', changePassword: 'Change password', changePasswordDesc: 'Re-encrypt your saved-to-device accounts with a new password. Leave the new password blank to remove it.', currentPassword: 'Current password', pwChanged: 'Password changed', enterPassword: 'Enter your password', enterPasswordDesc: 'Unlock your saved accounts to continue.', profUsername: 'Username', profUploadPic: 'Upload photo', profDisplayName: 'Display name', profUnameInvalid: 'Usernames can only use letters, numbers, dots, dashes and underscores.', lockSaveDesc: 'The accounts already saved on this device are protected by a password. Enter it to save this account with them and lock.', lockAction: 'Lock', password: 'Password', confirmPassword: 'Confirm password', pwMismatch: 'Passwords do not match.', pwWrong: 'Wrong password.', pwNeededToLock: 'Enter a password, or choose "Not now".', lockPwTitle: 'Protect this device?', lockPwDesc: 'Set a password to lock your wallet. Anyone using this device will need it to spend your money.', lockPwSkip: 'Not now', unlockSaved: 'Unlock saved accounts', unlockSavedDesc: 'Enter your password to open the accounts saved on this device.', unlock: 'Unlock', useAnotherWallet: 'Use another account', save: 'Save', publicKey: 'Public key', publicKeyDesc: 'Share this to watch this wallet read-only on another device or app. It reveals your addresses, not your keys.', copyKey: 'Copy key', showPublicKey: 'Show public key', createNew: 'Create new', importExisting: 'Import existing', genIntro: 'Generate a new 12-word seed phrase. Write it down. It’s the only way to recover this wallet.', generateSeed: 'Generate seed phrase', writeDownWarn: '⚠ Write these 12 words down on paper, in order. Anyone with them can spend your coins.', copyPhrase: 'Copy phrase', regenerate: 'Regenerate', verifyBackup: 'Verify backup', skipVerification: 'Skip verification', confirmBackupIntro: 'Confirm your backup by entering the requested words.', wordN: 'Word #{n}', reenterPassphrase: 'Re-enter passphrase', back: '← Back', cancel: 'Cancel', paste: 'Paste', openWallet: 'Open wallet', wordsMismatch: 'Those words don’t match your phrase. Check your backup.', passphraseMismatch: 'That passphrase doesn’t match the one you entered.', seedPhrase: 'Seed phrase', seedPlaceholder: 'enter your seed words separated by spaces', passphrase: 'Passphrase', passWhatsThis: 'What’s this?', passInfo: 'An optional 13th word: if someone gets hold of your 12 seed words, they still can’t open your wallet without this one. It’s not a login password and it doesn’t encrypt anything — each passphrase opens its own separate wallet, so to recover your funds you’ll need your seed words and this passphrase, exactly as written.', show: 'Show', hide: 'Hide', invalidSeed: 'Invalid seed phrase — check the spelling and word order.', importLabel: 'Recovery phrase or key', importPlaceholder: 'Your seed words, or an xpub / xprv', invalidImport: 'That is not a valid recovery phrase, xpub, or xprv.', importedKey: 'Imported key', importedKeyNote: 'This wallet was imported from an extended private key, so there is no recovery phrase. Back up that key itself to keep the wallet.', keysNote: 'Keys never leave your browser. Works fully offline. Save this page and open it from your filesystem.', howItWorks: 'How it works', installApp: 'Install app', hiwBasicsTitle: 'The basics', hiwBasics1: 'When you make a wallet, the page generates twelve random words — the keys to your coins. Anyone who has them can spend; if you lose them, the coins are gone for good. Write them down on paper before you put money in.', hiwBasics2: 'Your keys never leave your device. Coinos runs servers that ferry payments and messages, but they can’t touch your money. The app itself is one page — you can save it and open it offline.', hiwBasics3: 'It uses standard Bitcoin formats: the same twelve words restore your Savings in Sparrow, Electrum, or any other standard wallet, even if this site disappears.', hiwLnTitle: 'Spending and Savings', hiwLn1: 'Your money can sit in two accounts. Savings is ordinary Bitcoin on the blockchain — safest for money you’re keeping, but each move costs a mining fee and waits for a confirmation. Spending sends and arrives instantly, free or nearly free, and is still only yours: it runs on Ark, a newer layer on top of Bitcoin where every coin stays locked to your key, and it speaks Lightning, so you can pay and be paid by any bitcoin app or shop.', hiwGiftsTitle: 'Gifts', hiwGifts1: 'You can gift bitcoin with a link: whoever opens it claims the coins into their own wallet — no app, no signup, works even for someone brand new to bitcoin. Until a gift is claimed, you can cancel it and take the coins back.', hiwStandaloneTitle: 'Keep a copy', hiwStandalone1: 'The whole wallet fits in one file. Download it, keep it on a hard drive or USB stick, and open it in any browser — it runs from there with no server and no install, and your twelve words bring your money with you.', hiwStandaloneLink: 'Download the standalone wallet', moveMoney: 'Move money', moveToSpending: 'Into Spending', moveToSpendingDesc: 'Make money instantly spendable, with no fees to send it.', moveToSaving: 'Into Savings', moveToSavingDesc: 'Park money in ordinary on-chain Bitcoin.', moveToSavingBtn: 'Move to Savings', spendingLabel: 'Spending', savingLabel: 'Savings', balanceSwitch: 'Switch to {name}', movingLabel: 'Moving…', balance: 'Balance', pending: 'Pending', tabReceive: 'Receive', tabSend: 'Send', tabHistory: 'History', tabSettings: 'Settings', tabMessages: 'Chat', msgSend: 'Send', msgEmpty: 'No messages yet — say hi!', msgPlaceholder: 'Message #{channel}', msgPlaceholderPlain: 'Message…', msgMembers: '{n} members', msgEncrypted: 'End-to-end encrypted', msgAddPerson: 'Add person', msgNewChannel: 'New channel', msgAdmin: 'admin', msgAttachFailed: 'Attachment unavailable', msgAttach: 'Attach a picture', msgAttachTooBig: 'That file is too big (20 MB max)', msgUploadFailed: 'Upload failed — try again', msgPhoto: 'Photo', searchPeopleFor: 'Search people for {q}', msgEmojiSearch: 'Search emoji…', msgEmojiMore: 'More reactions', msgEmojiNone: 'No match', msgEmojiAddPack: 'Add an emoji pack', msgEmojiPackHint: 'Paste a pack link or naddr…', msgEmojiPackAdd: 'Add', msgEmojiPackHave: 'Added', msgEmojiPackRemove: 'Remove this pack', msgEmojiPackShare: 'Copy a link to this pack', msgEmojiPackAdded: 'Emoji pack added: {name}', msgEmojiPackBad: "Couldn't find an emoji pack there", msgEmojiPackLink: 'Emoji pack', msgEdited: '(edited)', msgReact: 'React', msgZap: 'Zap', zapTallyTitle: '{n} sats zapped', close: 'Close', playVideo: 'Play', openInYouTube: 'Open on YouTube', accountsThisOne: 'signed in', worthThen: 'Worth then', worthNow: 'Worth now', worthNowInline: '{v} now', currencyLabel: 'Show amounts in', feedTitle: 'Feed', alertsTitle: 'Notifications', alertsSub: 'Zaps, reactions and replies to your posts', alertsNew: '{n} new', alertsEmpty: 'Nothing yet. Zaps, reactions and replies to your posts will show up here.', alertZap: 'zapped you {sats}', alertReact: 'reacted {emoji}', alertBoost: 'boosted your post', alertReply: 'replied to you', alertMention: 'mentioned you', alertNoteGone: 'The post is no longer on your relays', postWho: 'Who reacted', feedFollow: 'Follow', feedFollowing: 'Following', feedFollowed: 'Following them', feedUnfollowed: 'Unfollowed', feedFollowingN: 'Posts from {n} people you follow', feedFollowing1: 'Posts from the one person you follow', feedNoFollowsShort: 'Follow people to fill this', feedNoFollows: "You don't follow anyone yet. Their posts will show up here.", feedFindPeople: 'Find people', feedEmpty: 'Nothing posted yet.', feedAttach: 'Attach a picture or video', postLike: 'Like', postBoost: 'Boost', postBoosted: 'Boosted', postBoostedAlready: 'You already boosted that', postQuote: 'Quote', postMore: 'More', postMute: 'Mute', postUnmute: 'Unmute', postMuted: 'Muted — their posts are hidden', postUnmuted: 'Unmuted', postBlock: 'Block and unfollow', postBlocked: 'Blocked', zapSending: 'Sending your zap…', msgReply: 'Reply', profNewPost: 'New post', profYourProfile: 'Your profile', profComposePh: "What's on your mind?", profPostBtn: 'Post', profPosted: 'Posted', publicChatTitle: '{name} community', publicChatDesc: 'Live chat — read along, sign in to join the conversation', publicChatSignIn: 'Sign in to chat', publicChatOpenApp: 'Open coinos', publicChatLoading: 'Loading messages…', msgDelete: 'Delete message', msgNoIdentity: "Your nostr signer isn't connected — reconnect it to send as yourself", msgSendFailed: 'No relay accepted the message', msgLockedChat: 'Your wallet is locked, so it can’t sign messages.', noteRefLink: 'view note', noteRefLoading: 'Fetching note…', noteRefNotFound: "Couldn't find that note on the relays.", msgSignerOff: "Your nostr signer isn't connected, so you can't post as yourself yet.", msgReconnect: 'Reconnect', msgDmsTitle: 'Messages', msgCommunitiesTitle: 'Communities', msgNewDm: 'New message', msgNoDms: 'No conversations yet.', msgShowAllDms: 'Show all {n} conversations', msgShowFewerDms: 'Show fewer', msgNoDmsYet: 'No messages yet — say hi!', msgDmPlaceholder: 'Message', msgDmEncrypted: 'Private message · end-to-end encrypted', msgYouPrefix: 'You:', msgOpen: 'Open', msgNpubPlaceholder: 'npub…', msgSearchPlaceholder: 'Name or npub…', zapNoBalance: "There's not enough in Spending to send a zap — move some money into Spending first.", zapBoardBtn: 'Move sats into Spending', onbBoardTitle: 'Top up Spending', onbBoardBody: 'Move some sats from Savings into Spending — payments there are instant, with no mining fees.', topUpFromSavings: 'Top up from Savings', arkRenewedHistory: 'Renewed', arkDepthNotice: 'Uncooperative exit costs about {fee} sats.', arkDepthFree: 'free', arkDepthBtn: 'Manage', arkExpiredNotice: '{amount} has passed its expiry. The server can still renew it — until then it can\u2019t be spent or exited.', arkDepthRenewed: 'Renewal requested', arkDepthRenewBtn: 'Renew now', arkCoinsTitle: 'Spending coins', arkCoinsIntro: 'Your Spending balance lives in {n} coins totalling {total}. Each can be pulled on-chain without the server’s help — at a cost that grows with the coin’s history.', arkCoinsIntroOne: 'Your Spending balance lives in a single coin of {total}. It can be pulled on-chain without the server’s help — at a cost that grows with the coin’s history.', arkCoinsColAmount: 'Amount', arkCoinsColExpiry: 'Expiry', arkCoinsColExit: 'Exit cost', arkCoinsColRenew: 'Renewal', arkCoinsChipRenewTitle: 'This coin’s share of the renewal fee — the fee is priced on all the coins together and rounded up once, so tiny shares add almost nothing', arkCoinsChipRenewTiny: '< 1 sat', arkCoinsChipExpTitle: 'Time until this coin needs renewal', arkCoinsChipExitTitle: 'What pulling this coin on-chain by itself would cost', arkFeeChain: 'On-chain fee', arkFeeService: 'Service fee', arkCoinsExpired: 'expired', arkCoinsExitTitle: 'Uncooperative exit', arkCoinsExitDesc: 'If the server vanished today, publishing every transaction in each coin’s history would cost about {fee} sats in mining fees. After a renewal it would be one fresh coin, one hop: about {after} sats.', arkCoinsRenewTitle: 'Renewal', arkCoinsRenewDesc: 'Your wallet renews coins automatically once they are close to expiring and renewal is free. Renewing early is an option if you would rather pay a small fee now to keep your exit cost low in the uncooperative case — the coinos server not responding when you want to bring your funds on-chain. A renewal replaces your coins with a single fresh one in the next round ({every}), which flattens their history. The fee depends on how close the coin is to expiry — the closer, the cheaper:', arkCoinsRoundEvery: 'rounds run every {n} minutes', arkCoinsRoundEveryOne: 'rounds run every minute', arkCoinsRenewNowFee: 'Renew now for {fee}', arkCoinsRenewSome: 'Renew {n} selected for {fee}', arkCoinsRenewNone: 'Select coins to renew', arkCoinsSelectAll: 'Select all', arkCoinsSpanDay: '1 day', arkCoinsSpanDays: '{n} days', arkCoinsSpanHour: '1 hour', arkCoinsSpanHours: '{n} hours', arkCoinsSchedOver: 'more than {span} left', arkCoinsSchedBetween: '{a} to {b} left', arkCoinsSchedFinal: 'less than {span} left', arkUniUneconomical: 'A trustless on-chain exit would cost about {fee} sats in mining fees — more than the {n} sats it would move, so it isn’t offered. For a balance this size, send it out over Lightning instead: no mining fees at all.', arkUniFallbackBody: 'The server didn’t cooperate, but your money can still move without it: your whole Spending balance exits to Savings on-chain. It takes about a day, and roughly {fee} sats of mining fees will be paid from Savings. Funds land in Savings — send them on from there once it completes.', arkUniFallbackShort: 'Savings holds {have} sats — about {need} sats are needed for the mining fees. Top up Savings first.', arkUniFallbackBtn: 'Exit to Savings ({fee} sats in fees)', msgPushOffer: 'Get notified when you receive payments and messages, even with coinos closed.', msgPushEnable: 'Enable notifications', msgPushOn: 'Notifications on', msgNotifyOn: 'Notifications on for this community — tap to mute', msgNotifyOff: 'Muted — tap to be notified about this community', msgOnline: 'online', msgNOnline: '{n} online', msgSeenJustNow: 'last seen just now', msgSeenMins: 'last seen {n} min ago', msgSeenHours: 'last seen {n}h ago', msgSeenDays: 'last seen {n}d ago', msgSeenRecently: 'last seen recently', msgTyping: '{name} is typing…', msgTyping2: '{a} and {b} are typing…', msgTypingMany: '{n} people are typing…', msgPushFailed: 'Could not enable notifications', msgChannelPlaceholder: 'New channel name', msgOwnerOnly: 'Only the community owner can do that', profEdit: 'Edit profile', profPay: '⚡ Pay', profName: 'Name', profAbout: 'About', profPicture: 'Picture URL', profBanner: 'Cover photo URL', profUploadBanner: 'Upload cover', profSaved: 'Profile published', hats: 'Hats', hatShopSupport: 'Support Coinos, Buy a Hat!', hatShopTitle: 'Hat shop', hatShopBlurb: 'Support Coinos and wear the proof on your avatar.', hatShopOffline: 'The hat stand is unreachable right now.', hatBeanie: 'Toque', hatParty: 'Party hat', hatTrucker: 'Trucker cap', hatCowboy: 'Cowboy hat', hatFedora: 'Fedora', hatBowler: 'Bowler', hatWizard: 'Wizard hat', hatTop: 'Top hat', hatConstruction: 'Hard hat', hatCrown: 'The Crown', hatKnight: 'Knight\u2019s helm', hatCrownWho: "Adam's. Sorry.", hatCrownTaken: 'not for sale', hatOwned: 'In your hat rack', hatWear: 'Wear', hatWearing: 'Wearing ✓', hatConfirmTap: 'Tap to confirm', hatYours: '🎩 The {name} is yours!', hatNoFunds: 'You need {sats} sats in Spending for that hat', hatNeedSpending: 'Open your Spending balance first', hatMainnetOnly: 'Hats are sold on mainnet and Mutinynet only', profNotesTitle: 'Latest posts', profNotesNone: 'No recent posts found.', profReplyTag: 'reply', msgSearching: 'Searching…', msgNoMatches: 'No matches', msgBadNpub: 'Not a valid npub', msgSignerNoDm: 'This signer cannot encrypt DMs — log in again', msgJoin: 'Join', msgCreate: 'Create', msgInvitePlaceholder: 'Paste an invite link, or search', msgDiscoverTitle: 'Suggested communities', msgHubCredit: 'From the Vector Hub', msgHubLoading: 'Finding communities…', msgHubFailed: 'Could not load suggestions right now.', msgJoined: 'joined', msgNamePlaceholder: 'Community name', msgBadInvite: 'Not a valid invite link', msgInviteLoading: 'Fetching invite…', msgInviteNotFound: 'Invite not found on its relays', syncStorageTitle: 'Sync storage', syncStorageHow: 'Wallet state too large for a relay event is encrypted and stored as a blob on these Blossom servers (one per line). Any Blossom server works — run your own or use another provider.', syncStorageSave: 'Save servers', syncStorageReset: 'Use default', syncStorageSaved: 'Sync storage servers saved', syncStorageInvalid: 'Enter at least one https:// server', syncStoragePending: '{n} snapshot(s) waiting to upload', msgInviteRevoked: 'This invite has been revoked', msgInviteExpired: 'This invite has expired', msgInviteTo: 'You are invited to {name}', msgInviteFounder: 'Founded by {npub}', msgDirectInvite: '{from} invited you to {name}', msgDismiss: 'Dismiss', msgInviteTitle: 'Invite people', msgCopyInvite: 'Copy invite link', msgLinkCopied: 'Invite link copied', msgInviteSent: 'Invite sent', msgLeave: 'Leave community', msgLeaveConfirm: 'Really leave? Rejoining needs a new invite', nostrSettings: 'Nostr', settingsWallet: 'Wallet', settingsPayments: 'Payments', settingsNetwork: 'Network', advancedSettings: 'Advanced', recoveryDesc: 'The twelve words that back up this wallet. Anyone who sees them can steal your funds.', receiveOfflineSpending: "You're offline — your payment address needs the network. You can still receive to Savings with an on-chain address.", receiveUseSavings: 'Switch to Savings', arkOffboardAmtPlaceholder: 'Amount', arkSplitting: 'Carving out that exact amount — this can take a minute…', arkSplitTimeout: 'The split is taking longer than expected — your balance is intact; try again shortly.', copyAddress: 'Copy address', paymentReceived: 'Payment received!', tapToProceed: 'Tap anywhere to proceed', recipient: 'Recipient', recipients: 'Recipients', max: 'Max', feeRate: 'Fee rate', feeEconomy: 'Economy', feeNormal: 'Normal', feePriority: 'Priority', feeCustom: 'Custom', selectedRate: 'Selected rate: {n} sat/vB', coinSelection: 'Coin selection', automatic: 'Automatic', manual: 'Manual', noCoins: 'No coins available.', nSelected: '{n} selected', addRecipient: '+ Add recipient', reviewTx: 'Review transaction', remove: 'Remove', enterRecipientAddr: 'Enter a recipient address.', enterAddrForN: 'Enter an address for recipient {n}.', enterValidAmtForN: 'Enter a valid amount for recipient {n}.', selectCoin: 'Select at least one coin to spend.', networkFee: 'Network fee', offlineSignNote: 'Offline: sign now, then broadcast the signed transaction from an online device.', unconfirmedInputWarn: 'This payment spends bitcoin that hasn’t confirmed yet, so it won’t confirm until that earlier transaction does.', signBroadcast: 'Sign & broadcast', signTx: 'Sign transaction', signExport: 'Sign & export (broadcast elsewhere)', scanQr: 'Scan QR', scanHint: 'Point the camera at a QR code', scanNoCamera: 'Could not access the camera.', scanNoDecoder: 'QR scanning needs a connection the first time it’s used.', scanUnrecognized: 'Couldn’t read a Bitcoin address from that QR.', broadcastScanned: 'Broadcast transaction?', broadcastNow: 'Broadcast', bumpFee: 'Bump fee', bumpConfirm: 'Replace with a higher fee?', replaceTx: 'Replace', bumpInsufficient: 'Not enough funds to bump at this rate. Try a lower rate.', scanOffline: 'You’re offline — reconnect to broadcast this transaction.', broadcastFailed: 'Broadcast failed: {msg}', broadcastTimeout: 'Timed out reaching the network. The payment was not sent — try again.', signingFailed: 'Signing failed: {msg}', done: 'Done', txSignedNote: 'Signed, but not sent yet. To actually send it, broadcast the code below from any online device (e.g. mempool.space → Broadcast). It will not send on its own.', transactionId: 'Transaction ID', signedTxRaw: 'Signed transaction (code)', copyHex: 'Copy code', downloadLabel: 'Download', showQrAirgap: 'Show QR (for offline transfer)', txBroadcast: '✓ Transaction broadcast!', copyTxid: 'Copy transaction ID', viewOnMempool: 'Block explorer ↗', noTxYet: 'No transactions yet.', loadingHistory: 'Loading transactions…', received: 'Received', sent: 'Sent', pendingTag: '⏳ pending', stuckTag: '⚠ stuck', stuckNote: 'Fee too low — may not confirm', stuckIncomingNote: '⚠ This payment’s fee is below the current network floor, so it may not confirm. Only the sender can speed it up (bump or re-send).', stuckOutgoingNote: '⚠ This transaction’s fee is below the current network floor, so it may not confirm on its own. Bump the fee below to rebroadcast it at a higher rate.', awaitingConfirmation: 'awaiting confirmation', feeShort: 'fee {x}', historyOffline: 'Transaction history is unavailable in offline mode.', status: 'Status', confirmed: 'Confirmed', confirmedTag: 'confirmed', block: 'Block', date: 'Date', copyId: 'Copy ID', backToHistory: '← Back to history', recoveryPhrase: 'Recovery phrase', giftWelcome: 'You’ve been sent Bitcoin', giftBackedUp: 'I’ve written it down', giftLink: 'Gift some Bitcoin', giftLinkDesc: 'Presign an amount as a link. Whoever opens it claims the Bitcoin into a brand-new wallet that only they control.', giftLinkWarn: '⚠ Anyone who opens this link can claim the amount, so share it only with the person you are gifting. Keep amounts small.', giftLinkReveal: 'Create gift link', giftAmountLabel: 'Amount to gift', giftAmountInvalid: 'Enter at least {n}.', giftMinNote: 'Minimum {n}.', giftAllNote: 'Gifts your whole balance; the recipient pays the claim fee.', giftExceedsBalance: 'That’s more than your balance.', giftSettling: '{n} of your balance is still settling — try again in a moment.', giftNeedsHeadroom: 'Gift up to {n}, or tap Max to gift your whole balance.', giftAnother: 'Create another', giftForYou: 'A gift for you', claimGift: 'Claim', dismiss: 'Dismiss', giftCopyCode: 'Copy claim code', giftQrTooLong: 'Too large for a QR code — share the link below instead.', giftConsolidateExplain: 'This gift would bundle {count} coins, making it costly for the recipient to claim. Splitting one coin first (~{fee}) gives them a clean single-input gift.', giftConsolidateFirst: 'Split into one coin first', giftUseManyCoins: 'Use {count} coins anyway', giftSplitExplain: 'This gift would lock {lock} until it’s claimed ({change} of it comes back to you then). You can split a right-sized coin first for about {fee} so only the gift amount stays locked.', giftSplitFirst: 'Split a coin first', giftLockWhole: 'Lock the whole coin ({n})', giftSplitFailed: 'Could not split the coin.', giftChecking: 'Opening your gift…', giftTakenTitle: 'Already claimed', giftTakenBody: 'Sorry, this gift has already been claimed.', createWalletAnyway: 'Create a wallet anyway', goToHome: 'Go to home', newWalletTitle: 'Your new wallet', newWalletBody: 'Back up your recovery phrase to keep it safe.', giftHistoryTitle: 'Gift link', giftUnclaimedTag: 'unclaimed', giftCoinLabel: 'Coin', giftClaimTxLabel: 'Claim', giftClaimedTag: 'claimed', giftView: 'Gift link', giftCodeLabel: 'Claim code (sent via DM)', giftCancel: 'Cancel', viewAllGifts: 'View all gifts ({n})', prevPage: '‹ Prev', nextPage: 'Next ›', pageXofY: 'Page {x} of {y}', lockedInGifts: 'Locked in gifts', giftReserved: 'Unclaimed gift links ({n})', giftReclaim: 'Reclaim', giftReclaimPrompt: 'Reclaim frees this coin for your next payment, but the link stays claimable until you spend it. Pay a network fee to revoke it right now instead?', giftReclaimed: 'Coin reclaimed', giftReclaimedTag: 'still claimable', giftRevoke: 'Revoke now', giftRevokeConfirm: 'Revoke this gift now? This spends the coin back into your wallet and disables the link.', giftRevoked: 'Gift revoked', giftRevokedTag: 'revoked', giftCopyLinkAgain: 'Copy gift link', copyLink: 'Copy link', claimBody: 'Claim it into a brand-new wallet that only you control.', claimBodyExisting: 'Claim it into your account \u201c{name}\u201d.', claimIntoPrompt: 'Choose where to receive it.', claimNewWallet: 'Receive into a new wallet', giftClaimedContext: 'This transaction delivered your gift', claimIntoExisting: 'Receive into one of your accounts:', claimOr: 'or', goToWallet: 'Go to wallet', claimBtn: 'Claim it', claimFeeNote: '~{n} fee', claimFailed: 'This gift has already been claimed or is no longer available.', claimNotVisibleBody: 'The coins funding this gift aren\u2019t visible from this wallet\u2019s network/data source. Make sure this wallet uses the same network and data source as the sender, then try again.', claimedTitle: 'Claimed!', claimedBody: 'Your Bitcoin is on its way. Back up your recovery phrase to keep it safe.', recoveryWarn: '⚠ Anyone who can see these words can steal your funds. Make sure nobody is watching before you reveal them.', bip39Passphrase: 'Passphrase', copyPassphrase: 'Copy passphrase', revealRecovery: 'Reveal recovery phrase', revealWords: 'Show the words', offlineTransfer: 'Offline transfer', offlineTransferDesc: 'Export your coins on an online device, then import the file on an offline device to sign without internet.', exportSnapshot: '⤓ Export snapshot', importSnapshot: 'Import snapshot…', rescan: 'Rescan', rescanDesc: 'Your current address updates automatically. Rescan an individual address only if a payment was sent to an old (reused) one and isn’t showing.', rescanWallet: 'Rescan wallet', rescanAddresses: 'Rescan an address', rescanAddrDesc: 'Tap Rescan next to the address a payment was sent to. Only that address is re-checked.', rescanOne: 'Rescan', rescanDone: 'Address rescanned', rescanFailed: 'Rescan failed', unusedTag: 'unused', scanning: 'Scanning…', explorer: 'Block explorer', explorerDesc: 'The server this wallet queries for balances and history. Point it at your own node for maximum privacy.', explorerUrl: 'Server URL', explorerUrlHint: 'Esplora / electrs REST base, e.g. https://mempool.space/api', walletName: 'Account name', walletSettings: 'Account settings', walletSettingsDesc: 'Name, recovery phrase, public key, and auto-logout for this account.', autolockTitle: 'Log out automatically', autolockDesc: "After the app has been in the background this long, log this wallet out — it's removed from this device (including from Save to device), so keep its recovery phrase. Other accounts aren't affected. The timer never runs while the app is open and focused.", autolockNever: 'Never', autolock1m: 'After 1 minute', autolock5m: 'After 5 minutes', autolock1h: 'After 1 hour', autolock1d: 'After 1 day', dataSource: 'Data source', dataSourceDesc: 'Where Coinos reads the chain and how it learns about incoming payments.', modeCoinos: 'coinos (instant)', modeCoinosDesc: "The default. Chain data and instant payments come from coinos's own Bitcoin node, with public servers as automatic backup.", modeExplorer: 'Block explorer', modeExplorerDesc: 'Reads from your chosen block explorer and polls for payments. A bit slower.', modeElectrum: 'Electrum server', backendElectrumDesc: 'Data and instant payments over one Electrum WebSocket. Point it at your own server for a fully self-hosted wallet.', electrumUrl: 'Electrum server (wss://)', electrumUrlHint: 'A WebSocket Electrum server, e.g. wss://your-node:50004', sourceUrl: 'Server URL', sourceUrlHint: 'wss:// for an Electrum server, or an Esplora REST base (https://…/api). The type is detected from the URL.', detectedElectrum: 'Electrum — data + instant payment notifications.', detectedEsplora: 'Block explorer (Esplora) — data only; polls for payments.', silentPaymentNote: 'Silent payment', receiveAddressTab: 'Savings', receiveLnTab: 'Lightning', arkReceiveIntro: 'Receive instantly and free — straight into Spending.', arkTitle: 'Spending balance', arkDesc: 'Your Spending balance sends and receives instantly with no mining fees, and stays yours — you can move it back to Savings whenever you like.', arkServerUrl: 'Server URL', arkEsploraUrl: 'Esplora URL (chain source)', arkWatchOnly: 'Spending needs the wallet keys — load the seed to use it.', arkConnecting: 'Connecting…', arkConnectBtn: 'Connect', arkNotConnected: 'Not connected — pick a server in Settings.', arkBalance: 'Spending', arkBoarding: 'Moving…', arkBoardHistory: 'Moved to Spending', arkVtxos: 'Coins (VTXOs)', arkPendingActions: '{n} operation(s) in progress', arkBoardBtn: 'Move to Spending', arkBoardFeeNote: 'Fee {fee} sats — {net} sats will arrive in Spending.', arkBoardFeeLabel: 'Fee', arkBoardedTitle: 'On its way!', arkBoardedNote: 'Ready to spend after {n} confirmations.', arkBoardedNoteOne: 'Ready to spend after 1 confirmation.', arkBoardIntro: 'Move money from Savings into Spending:', arkBoardAvailable: '{n} in Savings', arkBoardNoFunds: 'Needs at least {n} sats in Savings.', arkRefreshBtn: 'Refresh / consolidate {n} coin(s)', arkOffboardBtn: 'Move {n} to Savings', giftSourceChain: 'On-chain', giftSourceArk: 'Spending', giftArkNote: 'Sent from Spending — the link claims instantly, with no fee.', arkRenewWarn: 'Your Spending balance ({amount}) is too small to renew itself and will expire around {date}. Receiving any amount — or spending it — keeps it alive.', arkRenewForecast: 'Coins renew in ~{when} for {fee}.', arkRenewForecastNow: 'Coins renew shortly for {fee}.', arkBoardStuck: 'Moving money hit a snag and will keep retrying: {error}', arkRenewWarnPush: 'Your Spending balance is too small to renew itself and expires around {date}. Receive any amount or spend it to keep it alive.', giftArkOutstanding: '{n} unclaimed instant gift(s)', giftArkRevoked: 'Gift swept back into Spending.', claimArkFree: 'instant, no fee', arkGiftWrongNet: 'This gift lives on {net} — switch the network in Settings, then open the link again.', arkOffboardedTitle: 'On its way!', arkOffboardedNote: 'Arrives in Savings after 1 confirmation.', arkOffboarded: 'Moved to Savings', arkOffboardHistory: 'Moved to Savings', arkExitBtn: 'Move to Savings without the server', arkExitStarted: 'Unilateral exit started for {n} coin(s) — broadcasting the exit chain.', arkExitNoFeeCoin: 'Paused: the next step needs about {need} sats in Savings for its mining fee. Top up Savings and it resumes by itself.', arkExitFeeShort: 'Savings can’t cover the exit’s mining fees (about {need} sats needed, {have} available). Top up Savings first.', preimageLabel: 'Preimage', copyPreimage: 'Copy preimage', arkExitSummary: 'Moving {n} sats to Savings on-chain — about {eta} until it lands.', arkExitViewTx: 'View on block explorer', arkExitSoon: 'soon', arkExitRetrying: 'hit a snag — retrying automatically', arkExitCancel: 'Cancel this exit', arkExitLink: 'Move to Savings', arkExitPageTitle: 'Move money to Savings', arkExitPageIntro: 'Two ways to move your Spending balance into Savings.', arkCoopTitle: 'Cooperative (recommended)', arkCoopDesc: 'The normal way: one transaction into Savings, ready after a single confirmation, with a small fee taken from the amount.', arkUniTitle: 'Unilateral exit', arkUniDesc: 'Your escape hatch: claim the money yourself without anyone’s help. Takes about a day and costs mining fees, but works even if the server disappears.', arkExitNoBalance: 'Nothing in Spending to move.', arkExitingHistory: 'Moving to Savings', arkExitPendingTag: 'in progress', arkNoSpendableMidExit: 'No spendable balance — your coins are on their way to Savings in an on-chain exit.', arkExitInFlightLink: 'Exit in progress ({n} sats) — view details', arkExitCoopHeld: 'Your balance is in the unilateral exit below. While that exit hasn’t touched the chain yet, cancelling it frees the coins for a cooperative move.', arkZapTitle: 'Send a zap', awTitle: 'Auto-withdraw', awDesc: 'When Spending goes over an amount you choose, send it onward automatically — to an address, a payment name, a lightning address, or an npub.', awDest: 'Send to', awDestPh: 'name@domain, npub, bc1…, ark1…', awThreshold: 'When Spending reaches (sats)', awKeep: 'Keep behind (sats)', awTurnOn: 'Turn on auto-withdraw', awTurnOff: 'Turn off', awOn: 'Auto-withdraw is on', awOff: 'Auto-withdraw is off', awBadDest: "That destination isn't one we can pay", awLowThreshold: 'Use a threshold of at least 1,000 sats', awKeepTooHigh: 'Keep less behind than the threshold', awOutOfRange: "The amount is outside the destination's limits", awSent: 'Auto-withdrew {n}', awLast: 'Last sent {n} · {when}', awLastError: 'Last attempt failed: {why}', zapTitle: 'Send a zap', zapFinding: 'Finding the best way to pay them…', arkZapLookup: 'Looking them up…', arkZapNoArk: 'No way to pay this account was found — no Ark address, Lightning address, or payment record.', arkZapHint: 'Instant and free, with a zap receipt on nostr.', arkZapCommentPh: 'Comment (optional)', arkZapBtn: 'Zap', arkZapSentTitle: 'Zapped!', arkZapNoArkGift: 'They haven’t set up an Ark address yet — send the zap as an Ark gift locked to their nostr key instead. They’ll get a DM with the claim link, and only they can open it.', arkZapGiftBtn: 'Send locked gift', arkZapGiftHint: 'Instant Ark gift only their nostr key can claim. You can revoke it from the gift card while unclaimed.', arkZapGiftSentTitle: 'Gift sent!', giftDmText: '🎁 Someone sent you {amount} on coinos! Claim it here: {link}\nIf you’re asked for a claim code, use: {code}', giftDmSending: 'Sending them the claim link by DM…', giftDmSent: 'Claim link sent to them by DM ✓', giftDmFailed: 'Couldn’t reach them by DM — copy the link and claim code and pass them along yourself.', giftLockedTo: 'Locked to', giftCodeHint: 'Enter the claim code from your DM to unlock this gift.', claimCodePlaceholder: 'Claim code', claimCodeWrong: 'That code doesn’t unlock this gift.', claimWithExtension: 'Unlock with nostr extension', orEnterCode: 'or enter your claim code', extWrongAccount: 'Your extension is signed in to a different nostr account.', extNoNip44: 'Your extension doesn’t support NIP-44 decryption.', extDecryptFailed: 'Couldn’t decrypt with the extension.', arkOpsBoard: '{n} deposit(s) confirming', arkOpsSend: '{n} send(s) finishing up', arkOpsRefresh: '{n} refresh(es) waiting for the next round', arkOpsOffboard: '{n} withdrawal(s) confirming', arkOpsLnPay: '{n} Lightning payment(s) in flight', arkOpsLnRecv: '{n} Lightning invoice(s) outstanding', arkLnPayVia: 'Instant, from your Spending balance', arkLnFee: 'Fee', arkLnPaying: 'Paying invoice…', arkLnRetrying: 'Route failed — retrying with a bigger fee budget ({n}/{of})…', arkLnPayFailed: 'Payment failed', arkLnRefunded: 'Payment failed — your money is back in Spending.', arkLnPaidTitle: 'Paid!', or: 'or', nlSignIn: 'Sign in', feedOneNew: '1 new post', feedNNew: '{n} new posts', feedOneNewWord: 'new post', feedNNewWord: 'new posts', composePreview: 'Preview', composePreviewHide: 'Hide preview', composePreviewTag: 'Preview', composePreviewEmpty: 'Nothing to preview yet.', onbWelcomeTitle: 'Welcome to coinos', onbWelcomeBody: 'Bitcoin you actually hold. Your wallet is twelve words — nobody else ever has them, and they work in any wallet, anywhere.', onbStart: 'Create a new wallet', onbStartHint: "Takes a minute: write down twelve words and it's yours", onbOrSignIn: 'or, if you already have an account', onbBackupTitle: 'Write down your recovery phrase', onbBackupBody: "Signing in was the easy part — but the sign-in isn't your wallet. These twelve words are. They're the only way back in if you lose access to that account, and nobody can reissue them for you.", onbBackupDone: "I've written it down", onbBackupLater: 'Later', backupReminder: 'Your recovery phrase is still unwritten.', backupReminderGo: 'Write it down', onbSeedTitle: 'Set up your wallet', onbSigninTitle: 'Sign in', onbLegacyTitle: 'Move your coinos.io account', onbLegacyBody: 'The original coinos. We can move your balance over and point your username here — your old account keeps working, it just stops receiving payments.', onbLegacyGo: 'Move my coinos.io account', onbLegacyNoAddr: "Your payment address isn't ready yet — try again in a moment.", onbLegacyDone: "I'm done — continue", onbHaveCoinos: 'Migrate an existing coinos account', migrateClaimed: 'Welcome back — {name} is yours here now.', migrateClaimFailed: "Your balance moved, but {name} isn't free yet. Try claiming it in a minute.", migrateWorking: 'Claiming {name}…', onbSpendTitle: 'Set up a spending account', onbSpendBody: 'Claim a username and get your own payment address — people can send you bitcoin instantly, with just your name.', onbNameWait: 'Getting things ready…', onbNotNow: 'Not now', onbAvatarTitle: 'Choose your look', onbAvatarBody: 'Pick a punk or upload your own picture. You can change it anytime.', onbUpload: 'Upload a photo', onbUploadFailed: 'Upload failed — try a punk for now', onbProfileSkipped: 'Couldn’t publish your profile — set it later from Edit profile', onbContinue: 'Continue', onbSkipAvatar: 'Continue with my default', onbDoneTitle: "You're all set!", onbDoneBody: 'Your spending account is set up — your address is live and the coinos community chat is waiting for you.', onbEnter: 'Enter coinos', spendSetup: 'Set up a spending account', nlConnectApp: '📱 Connect a signer app', nlOpenSigner: 'Open signer app', nlWaitingSigner: 'Waiting for your signer to connect…', nlTitle: 'Log in with Nostr', nlSignInNostr: 'Sign in with Nostr', nlDesc: 'Use your Nostr account to open your wallet — on any device, with the same account.', nlExtension: 'Use browser extension', nlGoogle: 'Sign in with Google', nlPasskey: 'Sign in with a passkey', nlKeyOrBunker: 'nsec, private key, or bunker://…', nlGo: 'Log in', nlHint: 'Works with Amber, Amethyst and other remote signers (paste their bunker:// address), or a browser extension. A pasted key is used once and never stored.', nlApprove: 'Approve the request in your signer →', nlUnrecognized: 'That doesn’t look like a private key or a bunker:// address.', nlNewTitle: 'Create a wallet for this account', nlNewDesc: 'This Nostr account has no wallet yet. We’ll make one and save it, encrypted to your account, so you can open it anywhere you use this account.', nlBackupWarning: 'Your wallet will be recoverable by anyone who controls this Nostr account — treat that account like a key to your money.', nlCreateBtn: 'Create my wallet', nlLinkTitle: 'Nostr account', nlLinkDesc: 'Log in with your own Nostr account instead — chat and your payment address will use its identity, and logging in with it on another device opens this wallet.', nlBuiltinDesc: 'This wallet has a built-in Nostr account, derived from its seed. Chat and your payment address already use it — you don’t need to link anything.', nlExportBtn: 'Export Nostr key', nlExportWarn: 'This key opens this wallet. Anyone who has it can spend your money, read your chats, and change your payment address — guard it like your recovery phrase.', nlExportReveal: 'Show the key', nlExportedNote: 'Logging in with Nostr using this key opens this wallet.', nlChangeBtn: 'Use a different Nostr account', nlLinkedDesc: 'This wallet opens when you log in with this Nostr account.', nlConnected: 'connected', nlDisconnected: 'not connected', nlReconnectDesc: "Your signer isn't connected, so this app can't sign as you. Reconnect it to post and update your profile — your wallet and balance aren't affected.", nlReconnectTitle: 'Reconnect your signer', nlReconnectBody: 'Your Nostr signer isn’t connected on this device. Reconnect to keep acting as {npub}…', nlWrongAccount: 'That signer is a different Nostr account. Reconnect with {npub}.', nlLinked: 'Wallet linked to your Nostr account', nlNeedSeed: 'This wallet has no recovery phrase loaded.', nlAlreadyLinked: 'That Nostr account already has a different wallet.', namesAutoFailed: 'Couldn’t set your address up automatically ({why}). Pick one below.', namesSettingUp: 'Setting up your payment address…', namesTitle: 'Payment address', receiveNameTab: 'Spending', namesCustom: 'Change username', namesCustomHow: 'Pick a new name for your payment address. Your old address stops working.', namesMerchantShow: 'I\u2019m a merchant \u2014 show point-of-sale tools', namesMerchantHide: 'Hide point-of-sale tools', namesPos: 'Point of sale', namesPosHow: 'A coinos POS terminal takes payments to this address. Create a token and enter it as the API token when you flash the terminal at coinos.io/pos. The terminal can ring up sales and see them paid; it can never spend.', namesPosCreate: 'Create POS token', namesPosCopy: 'Copy token', namesPosRevoke: 'Revoke tokens', namesPosRevoked: 'POS tokens revoked', namesPosLink: 'Tap-to-pay link', namesPosLinkHow: 'What the terminal broadcasts over NFC. Any lightning wallet that scans it pays the sale the terminal just rang up, or asks for an amount. Print it as a sticker if you like.', namesPosCopyLnurl: 'Copy LNURL', namesPosCopyLink: 'Copy link', namesDesc: 'Claim your payment address — like you@coinos.io. People can pay you just by typing it, no codes to copy or QR to scan.', namesPlaceholder: 'username', namesResolving: 'Looking up {name}…', namesSendTo: 'Send to {name}', namesClaim: 'Claim', namesClaimed: 'You are now {name}', namesCopy: 'Copy address', namesMoreOpen: 'More options', namesMoreTitle: 'More ways to get paid', namesMoreBolt11: 'Lightning invoice (BOLT 11)', namesMoreBolt11How: 'A one-time invoice for a specific amount', namesMoreBolt12: 'Lightning offer (BOLT 12)', namesMoreBolt12How: 'A reusable offer — mining pools like Ocean pay to one', namesMoreArk: 'Ark address', namesMoreArkHow: 'Get paid directly from any Ark wallet', namesMoreClink: 'Nostr offer (CLINK)', namesMoreClinkHow: 'A static code Nostr wallets can pay', namesMoreOnchain: 'Bitcoin address (on-chain)', namesMoreOnchainHow: 'Switch to Savings for a regular on-chain address', namesInvAmountPh: 'Amount (sats)', namesInvMemoPh: 'Description (optional)', namesInvCreate: 'Create invoice', namesInvNew: 'New invoice', namesInvBadAmount: 'Enter an amount in sats', namesOfferMemoPh: 'Label (e.g. Ocean payout)', namesOfferCreate: 'Create', namesOfferNoMemo: 'No label', namesOfferQr: 'QR', namesRelease: 'Release', namesReleased: 'Name released', namesNeedArk: 'Your wallet is still connecting — try again in a moment', namesNotFound: 'No payment record found for {name}', namesNoUsableInstruction: 'That address exists, but this wallet has no way to pay it', namesZapCode: 'Get zapped on Nostr', namesZapCodeHow: 'Paste this code into your Nostr app profile and zaps land straight in this wallet:', namesZapCodeCopy: 'Copy code', namesOwnDomain: 'Use your own domain', namesOwnDomainHow: 'Have your own domain? Add this line to its DNS records (DNSSEC must be enabled) and your address works there too:', msgMarkAllRead: 'Mark all read', namesOwnNode: 'Deliver to your own node', namesOwnNodeMenuHow: 'Run a Lightning node? Point your address at it — payments land there instead of in this wallet.', namesOwnNodeHow: 'Run your own Lightning node? Paste the CLINK offer code (noffer1…) from a service beside it — Lightning payments to your address will be delivered straight to your node instead of into this wallet.', namesOwnNodeActive: 'Lightning payments to your address are delivered to your own node:', namesOwnNodeClear: 'Deliver to this wallet again', namesOwnNodeSet: 'Delivery updated — payments go to your node', namesOwnNodeCleared: 'Payments land in this wallet again', namesOwnNodeNoRelay: 'That offer code names no relay', namesOwnLnoHow: 'Or paste your node’s BOLT12 offer (lno1…) — it goes into your address’s DNS record, so senders fetch invoices straight from your node.', namesOwnLnoActive: 'Your address’s DNS record carries your own node’s offer:', namesOwnLnoBad: 'That doesn’t look like a BOLT12 offer', bolt12Currency: 'This offer is priced in {currency}, which this wallet can’t pay', clinkBadInvoice: 'The recipient’s service returned an invoice that doesn’t match', clinkFixedAmount: 'This offer has a fixed price', nwcTitle: 'Nostr Wallet Connect', nwcDesc: 'Let another app pay from this wallet over nostr — it never sees your keys, only spends within the limits you set, and you can cut it off at any time.', nwcTabWarning: 'Works while Coinos is open — turn on background answering below to keep working when it isn’t.', nwcAdd: 'Add a connection', nwcBackground: 'Answer while closed', nwcNoPushHere: 'This browser can\u2019t receive push messages.', nwcNoPermission: 'Notifications weren\u2019t allowed.', upNoDistributor: 'To be reached while this app is closed you need a push app installed alongside it — ntfy is the usual one (F\u2011Droid or the Play Store). Install it, open it once, then reopen coinos. Until then, payments and messages arrive whenever coinos is open.', upWaiting: 'Waiting for {app} to hand over a push address \u2014 reopen coinos in a moment and it should be here.', nwcNoPushService: 'This browser has no push service, so it can\u2019t be woken while closed. On Android that\u2019s usually a phone without Google Play Services \u2014 Firefox brings its own push and works there. Zaps are still answered whenever coinos is open.', nwcOn: 'On', nwcOff: 'Off', nwcBackgroundOn: 'Requests will be answered in the background', nwcBackgroundOff: 'Background answering turned off', nwcPaidTitle: 'Payment sent', nwcPaidBody: '-{amount} sats via {name}', settingsNotifications: 'Notifications', searchUsers: 'Search', spendingName: 'Spending', manageAccounts: 'Accounts', lockedToast: 'Locked', logout: 'Log out', lockedTitle: 'Locked', lockedDesc: 'Your wallets are locked on this device. Enter your password to open them again.', settingsWalletDesc: 'Name, keys, backup', settingsPaymentsDesc: 'Zaps and auto-withdraw', settingsNetworkDesc: 'Servers and explorers', settingsNostrDesc: 'Identity and connections', settingsNotifDesc: 'What buzzes this device', settingsAdvancedDesc: 'Rarities and rescues', unlockForLabel: 'Stay unlocked', unlockFor5m: '5 minutes', unlockFor30m: '30 minutes', unlockFor1d: '1 day', unlockForever: 'Until I lock it', viewWithoutPassword: 'View without password', arkExitSendTitle: 'Send on-chain', arkExitSendNote: 'Leaves your Spending balance and arrives on-chain — network fees come off what arrives.', savingsName: 'Savings', renameWallet: 'Rename', addWalletSeed: 'Seed phrase', addWalletNewSeed: 'New seed phrase', addWalletShareSeed: 'Same seed as {name}', addWalletShareNote: 'Backs up under the existing phrase, but with its own separate coins, history, and identity.', addWalletGo: 'Continue', zapSentShort: 'Zapped {n}', zapSetupTitle: 'Set your zap amount', zapSetupDesc: 'One tap on ⚡ sends this amount instantly — no form, no confirmation. You can change it any time in Settings → Payments.', zapSetupSave: 'Save and zap', zapDefaultLabel: 'One-tap zap amount', zapDefaultHint: 'What a single tap of the ⚡ button sends.', threadNoReplies: 'No replies yet.', threadReplyHint: 'Write a reply…', threadReplySend: 'Reply', threadReplied: 'Reply published', threadRootMissing: 'The original post could not be found.', searchUsersHint: 'Name, npub, or payment address…', searchUsersEmpty: 'Find anyone on nostr and open their profile.', searchNoResults: 'No one found.', notifTitle: 'Notifications', notifEnable: 'Enable notifications on this device', notifEnabled: 'Notifications enabled', notifOnDevice: 'Notifications are on for this device.', notifPayRecv: 'Payments received', notifDm: 'Direct messages', notifMention: 'Replies and mentions on nostr', notifChatHint: 'Community chat is opt-in per room — turn it on from a room’s menu.', notifPaySent: 'Payments sent by connected apps', notifPaySentHint: 'A heads-up when a connected app — a nostr client zapping, for example — spends from this wallet while it’s closed.', nwcNotifHint: 'You’ll be asked to allow notifications — the connection uses them to answer when Coinos is closed.', nwcNotifNeeded: 'Without notifications the connection couldn’t answer when Coinos is closed, so it wasn’t added. Allow notifications and try again.', nwcNotifBlocked: 'Notifications are blocked for this site. Allow them in your browser settings, then add the connection again.', nwcNoPush: 'This browser can’t receive push notifications, so a connection wouldn’t work while Coinos is closed.', nwcAppName: 'App name (e.g. Damus)', nwcMaxPayment: 'Max per payment (sats)', nwcDailyBudget: 'Daily budget (sats)', nwcCreate: 'Create connection', nwcShow: 'Show connection string', nwcCopy: 'Copy connection string', nwcRevoke: 'Revoke', nwcRevoked: 'Revoked {name}', nwcLimits: 'Max {max}/payment · {day}/day · {spent} spent today', arkLnFundsPending: 'Your money is settling from a previous payment — retrying…', lnZapOffer: 'Zap {addr}', arkLnInvoiceIntro: 'Or take a Lightning payment, straight into Spending.', arkLnPaidHistory: 'Sent', arkLnReceivedHistory: 'Received', arkLnCancelInvoice: 'Cancel invoice', arkExited: 'Moved to Savings', arkExitHistory: 'Moved to Savings', arkRefreshStarted: 'Refresh submitted — completes with the next round.', arkSendTitle: 'Send', arkSendHint: 'Ark address — sent instantly off-chain, no mining fee.', arkOwnAddrBoard: "That's your own Spending address — moving from Savings instead.", arkLnExceedsSpending: 'That invoice asks for {need} but Spending holds {have}.', arkPayFromSpending: 'Ark payments come from your Spending balance — switch to Spending to send this.', arkPayTo: 'To', arkNoFee: 'none — instant off-chain send', arkSendBtn: 'Send', arkSentTitle: 'Sent!', arkBoarded: 'Boarded to Ark', dateLabel: 'Date', detailsLabel: 'Details', arkVtxoId: 'Coin (VTXO)', arkVtxoInputs: 'Coins spent', lnReceiveIntro: 'Receive over Lightning — paid into this wallet on-chain, non-custodial.', lnReceiveAwaiting: 'Share this invoice. When it’s paid, the sats land here automatically.', lnReceiveWatching: 'Waiting for payment…', lnReceived: 'Received!', lnCreateInvoice: 'Create invoice', copyInvoice: 'Copy invoice', amountSatsLabel: 'Amount (sats)', destPlaceholder: 'Address or Lightning invoice', lnQuoting: 'Getting a quote…', lnPayTitle: 'Pay over Lightning', lnPayAmount: 'Amount', lnPayBoltzFee: 'Swap fee', lnPayNetworkFee: 'Network fee', lnPayTotal: 'Total', lnPayConfirm: 'Pay', giftChainWarn: '⚠ This is a bearer link: whoever opens it can claim the gift, so share it privately. It funds a fresh on-chain wallet when claimed.', giftRetired: 'This gift link uses an older format that’s no longer safe to claim. Ask the sender to void it and create a new one.', lnAutopayOffer: 'Auto-pay {name} up to {n} next time', lnAutopaySetLabel: 'Auto-pay {name} next time, up to:', lnPaySentTitle: 'Lightning payment sent', lnPaySentBody: 'The swap provider pays the invoice once the on-chain lockup confirms.', lnZapTitle: 'Zap over Lightning', lnZapReviewTitle: 'Zap over Lightning', lnZapFallback: 'Zap over Lightning instead', lnZapResolving: 'Finding their Lightning address…', lnZapRequesting: 'Requesting a Lightning invoice…', lnZapNoAddress: 'This nostr user hasn’t published a Lightning address.', lnZapRange: 'Enter {min}–{max} sats', lnZapHintNostr: 'Sent over Lightning, with a public zap receipt on nostr.', lnZapHintPlain: 'Sent over Lightning (this address doesn’t support zap receipts).', lnZapSentBody: 'Your zap is on its way — the swap provider pays it once the on-chain lockup confirms.', lnZapFailed: 'Couldn’t complete the zap.', language: 'Language', darkMode: 'Dark mode', lightMode: 'Light mode', offlineBanner: "Can't reach the network — working offline. Import a snapshot in Settings (under Advanced) to load coins.", retry: 'Retry', copied: 'Copied to clipboard', snapshotExported: 'Snapshot exported', importedNCoins: 'Imported {n} coin(s)', unmatchedSuffix: ' · {n} address(es) not in this wallet', importFailed: 'Import failed: {msg}', copy: 'Copy', switchUnit: 'Switch BTC / sats', footerMadeBy: 'made by', footerSourceOn: 'source available on', }, }; // --- locale loading ------------------------------------------------------- // Only English is bundled; other languages are fetched on demand from // locales/.json (emitted by the build) and cached, so each visitor only // downloads the language they use. t() falls back to English until it loads. const _inflight = new Map(); export function loadLocale(code) { if (code === 'en' || STR[code] || !SUPPORTED.has(code)) return Promise.resolve(); if (_inflight.has(code)) return _inflight.get(code); const p = fetch('locales/' + code + '.json') .then((r) => (r.ok ? r.json() : null)) .then((d) => { if (d) STR[code] = d; }) .catch(() => {}) .finally(() => _inflight.delete(code)); _inflight.set(code, p); return p; } // --- runtime -------------------------------------------------------------- const SUPPORTED = new Set(LANGS.map(([c]) => c)); const LANG_KEY = 'btc-wallet-lang'; function detect() { try { const q = new URLSearchParams(location.search).get('lang'); if (q && SUPPORTED.has(q)) return q; } catch {} try { const saved = localStorage.getItem(LANG_KEY); if (saved && SUPPORTED.has(saved)) return saved; } catch {} try { for (const l of navigator.languages || [navigator.language || 'en']) { const code = String(l).toLowerCase().split('-')[0]; if (SUPPORTED.has(code)) return code; } } catch {} return 'en'; } let lang = detect(); export const getLang = () => lang; export function setLang(l) { if (!SUPPORTED.has(l)) return; lang = l; try { localStorage.setItem(LANG_KEY, l); } catch {} } export function t(key, vars) { let s = (STR[lang] && STR[lang][key]) ?? STR.en[key] ?? key; if (vars) for (const k in vars) s = s.replaceAll('{' + k + '}', vars[k]); return s; }