import { assetUrl } from './asset-url.js'; // 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', profPublicKey: 'Nostr public key', profKeyLink: 'Public key', followHistTitle: 'Follow list', followHistDesc: 'You follow {n} people. If another app replaced your follow list, an older copy may still be on a relay that keeps history. Restoring adds everyone from it back; nobody is removed.', followHistLook: 'Look for older copies', followHistPeople: '{n} people', followHistRestore: 'Restore {n}', followHistNone: 'No older copy has anyone your list is missing.', relaysTitle: "Relays", relaysDesc: "Where your posts go and where other apps look for you. The lists are signed with your key, so every Nostr app you use follows them.", relaysPublic: "Posts and profile. Read: where replies and mentions reach you. Write: where your posts are published.", relaysInbox: "Direct message inbox: where people send you private messages.", relaysRead: "Read", relaysWrite: "Write", relaysNoneYet: "Not published yet. These are the defaults; save to publish them.", relaysDown: "unreachable", relaysBadUrl: "That isn’t a relay address (wss://…).", relaysNeedBoth: "Keep at least one read relay and one write relay.", relaysNeedDm: "Keep at least one inbox relay.", relaysUnchanged: "Nothing changed.", relaysWrongIdentity: "Your signer belongs to a different account. Switch to it and try again.", relaysChangedElsewhere: "Another app just updated your relay lists. They’ve been reloaded; make your changes again.", relaysSaved: "Relay lists saved", relaysSave: "Save", relaysSaving: "Saving…", relaysReload: "Reload", followHistNothing: 'Everyone in that copy is already in your list.', followHistRestored: 'Restored {n} follows.', profFollowing: 'Following', profFollowers: 'Followers', profFollowsNone: 'Not following anyone yet.', profFollowersNone: 'No followers found.', profFollowersPartial: 'Showing {n} of {total} followers.', showMore: 'Show more', profFetchFailed: 'Couldn’t load the latest profile.', profNotFound: 'No profile details found on the available relays.', 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, with no app and no signup, and it 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. Check your connection and try again', mediaUploadTooBig: 'That file is too big for the available media hosts', mediaUploadUnsupported: 'That file format is not supported', 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', videoUnmute: 'Unmute', videoTapToPlay: "Tap to play", videoNoPicture: "This browser can’t show this video’s picture", videoOpenFile: "Open the file", videoLargeTitle: "Videos", videoLargeToggle: "Autoplay large videos", videoLargeHelp: "Videos over {mb} MB or above 1080p wait for a tap, so the feed doesn’t eat your data. The limit is lower on a slow or data-saving connection.", videoMute: 'Mute', openInYouTube: 'Open on YouTube', accountsThisOne: 'signed in', worthThen: 'Worth then', worthNow: 'Worth now', worthNowInline: '{v} now', currencyLabel: 'Show amounts in', feedTitle: 'Feed', feedFollowingName: 'Following', feedNew: 'New feed', feedNewName: 'New feed', feedEdit: 'Edit feed', feedName: 'Name', feedNameHint: 'e.g. Bitcoin builders', feedPeople: 'People', feedFollowsToggle: 'Everyone I follow', feedListsToggle: 'Everyone on my lists and packs', feedInterestsToggle: 'Posts from anyone on hashtags I follow', feedInterestsHelp: 'Your topic feeds\' hashtags and the interests on your nostr profile.', feedAddPerson: 'Add someone…', feedTopics: 'Topics', feedTopicsHint: '#bitcoin #nostr', feedTopicsHelp: 'Posts tagged with any of these. With people set as well, only their posts on these topics.', feedDelete: 'Delete feed', feedSaveAdhoc: 'Save feed', feedSaved: 'Feed saved', feedPostFab: 'Post', mediaShrinking: 'Shrinking video for streaming', mediaUploading: 'Uploading', feedNoQuery: 'Add people or topics to this feed.', feedOfTitle: '{name}’s timeline', feedOfLoading: 'Reading who they follow…', feedNoFollowsOf: 'They follow nobody yet.', feedShare: 'Share this feed', feedMenu: 'Feed options', feedSharedName: 'Shared feed', feedPackName: 'Follow pack', profTheirFeed: 'Their timeline', feedFirehose: 'Everything', feedPopular: 'Popular', askSignInTitle: 'Sign in to join in', askSignInBody: 'Reading is free. To zap, reply, react or follow, sign in or create an account.', askSignInCreate: 'Create a new account', askNotNow: 'Not now', feedLangLabel: 'Language', mediaAll: "All", mediaImages: "Images", mediaVideos: "Videos", mediaArticles: "Articles", mediaNoArticles: "No articles in this feed yet.", mediaNoImages: "No pictures in this feed yet.", mediaNoVideos: "No vertical videos in this feed yet.", feedRelays: 'Relays', feedRelaysFormHelp: 'Read this feed from these relays only. Leave empty for the usual ones.', feedAllToggle: 'Every post on these relays (a firehose)', feedAllCurated: 'Only popular posts: hide bots and posts nobody reacted to', feedAllNeedsRelays: 'Name the relays to read everything from.', feedRelaysHint: 'Changing these relays changes where posts come from, not which people or topics the feed follows.', feedRelayUsual: 'usual', feedRelaysOutbox: 'Right now, each person’s posts come from the relays they post to.', feedRelayPh: 'wss://relay.example.com', feedRelayAdd: 'Add', feedRelayOther: 'Other…', feedRelayPick: 'Add a relay…', feedRelayBad: 'That is not a relay address.', feedRelaysDefault: 'Back to the usual relays', feedRelaysDone: 'Done', feedSumFollows: 'Everyone you follow', feedSumFollowsPlus: 'Everyone you follow + {n} more', feedSumPeople: '{n} people', feedPacks: 'Follow packs', posTitle: 'Point of sale', posDesc: 'Charge a customer a set amount, with an optional tip prompt. Bills and tips are counted separately.', posOpen: 'Open', posEnterAmount: 'Enter an amount first', posNeedsSpending: 'Point of sale takes Lightning into Spending. Connect first', posNoteHint: 'Note (optional)', posCharge: 'Charge', posAskTip: 'Ask the customer for a tip', posTipChoices: 'Tip choices', posYourBill: 'Your bill', posAddTip: 'Add a tip?', posTipCustom: 'Other', posNoTip: 'No tip', posTotalLine: 'Total {n}', posContinue: 'Continue', posScanToPay: 'Scan to pay', posBreakdown: 'Bill {bill} + tip {tip} {u}', posWaiting: 'Waiting for payment…', posPaid: 'Paid', posNewSale: 'New sale', posInvoiceFailed: 'Could not create an invoice', posInvoiceExpired: 'That invoice expired before it was paid', posMemo: 'Bill {bill}, tip {tip}', posSalesToday: 'Today', posBills: 'Bills', posTips: 'Tips', posTotal: 'Total', posTipWord: 'tip', posVoid: 'cancelled', posAwaiting: 'awaiting payment', posMakingInvoice: 'Creating the invoice…', posBackspace: 'Delete', posNoDetail: 'No payment record for this sale yet', posNoteLabel: 'Note', posModeToggle: 'Start in point of sale on this device', posLinkHint: 'The till can also be bookmarked or added to the home screen: {url}', linkHandlerTitle: 'Open links here', linkHandlerDesc: 'Make this app the one your browser opens for nostr links (what njump calls "Your default web app") and bitcoin: links.', linkHandlerNostr: 'nostr links', linkHandlerBitcoin: 'bitcoin: links', linkHandlerAsked: 'Your browser is asking. Allow it to finish', linkHandlerRefused: 'Your browser would not register this here', feedAddPack: 'Search packs, or paste a following.space link…', feedPacksHelp: 'A pack is a list someone curates. Its members are read live, so the feed follows the list as it changes.', feedPackMembers: '{n} members', feedPackUntitled: 'Untitled pack', feedShareList: 'Keep as a nostr list', feedShareListHelp: 'Published under your key as a follow set (NIP-51), so other nostr apps show the same list. Untick to remove the list from the relays; the feed stays here.', feedPrivateMember: 'Private member, added in another app', feedListPeople: '{n} people', postCopyLink: 'Copy link', postShare: 'Share', listAddTo: 'Add to a list', listPickTitle: 'Lists', listPickEmpty: 'No lists yet. A feed of people is a list.', listNew: 'New list…', profLists: 'Lists', searchTopicRow: 'Posts on this topic', searchTopicFor: 'Posts tagged #{q}', 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: 'Not a post your relays have — perhaps a private message', 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.', feedLoadingMore: 'Loading more posts…', feedEnd: 'You’re all caught up.', feedAttach: 'Attach a picture or video', postLike: 'Like', postBoost: 'Boost', postBoosted: 'Boosted', postBoostedAlready: 'You already boosted that', postQuote: 'Quote', postMore: 'More', postDiscardTitle: 'Discard this draft?', postDiscardBody: 'You’ll lose this draft and any attachments.', postKeepEditing: 'Keep editing', postDiscard: 'Discard draft', postMute: 'Mute', postUnmute: 'Unmute', postMuted: 'Muted. Their posts are hidden', postUnmuted: 'Unmuted', postBlock: 'Block and unfollow', postMuteThread: 'Mute this thread', postUnmuteThread: 'Unmute this thread', postReport: 'Report…', postEdit: 'Edit', translateBtn: 'Translate', translating: 'Translating…', translated: 'Translated', translatedFrom: 'Translated from {lang}', translateShowOriginal: 'Show original', translateSame: 'This post is already in your language', translateNothing: 'There are no words in this post to translate', translateFailed: 'Couldn’t translate this post', translateTitle: 'Translation', translateAutoToggle: 'Translate posts into {lang} automatically', translateHelp: 'Posts that look like they’re in another language are shown in yours, with a way back to the original. Translations come from your browser when it can, otherwise from coinos’s own LibreTranslate server — posts are public, but the text passes through it.', postEditTitle: 'Edit post', postEditHelp: 'The edit is published beside the original (kind 1010). Apps that read edits show the new text; others keep showing what you first wrote.', postEditedTag: 'edited', postEditedToast: 'Post edited', postDelete: 'Delete', postDeleteTitle: 'Delete this post?', postDeleteHelp: 'A deletion request goes to the relays (NIP-09). Most honour it and apps hide the post, but anyone who already copied it may keep it.', postDeletedToast: 'Post deleted', postDeletedNote: 'This post was deleted by its author.', postNotYours: 'This post was signed by a key that isn’t signed in here', reportTitle: 'Report this post', reportHelp: 'A report is public (NIP-56). People who follow you see this post folded; the post is hidden for you.', reportSpam: 'Spam', reportImpersonation: 'Impersonation', reportNudity: 'Nudity', reportProfanity: 'Harassment or abuse', reportIllegal: 'Illegal content', reportMalware: 'Scam or malware', reportOther: 'Something else', reportSent: 'Reported', foldReported1: 'Reported by {name}', foldReportedN: 'Reported by {n} people you follow', foldShow: 'Show', foldSensitive: 'Sensitive content', foldSensitiveWhy: 'Sensitive: {why}', modSensitiveToggle: 'Fold posts marked sensitive', modSensitiveHelp: 'Posts their authors marked with a content warning (NIP-36) stay behind it until you tap Show, and are left out of picture and video grids. Popular never shows them, nor posts from accounts that say they post adult content.', quoteHidden: 'Hidden post', profMutedBanner: 'Muted. Their posts are hidden everywhere else', modTitle: 'Muted and hidden', modDesc: 'Your mute list (NIP-51) follows you to other nostr apps, and theirs applies here.', modPeople: 'Muted people', modNoPeople: 'Nobody muted', modWords: 'Hidden words and hashtags', modWordHint: 'Add a word, phrase or #hashtag', modAdd: 'Add', modThreads: '{n} muted threads', modSpamToggle: 'Hide repeated posts from strangers', modSpamHelp: 'A post of 60+ characters posted again and again is spam; someone you don\'t follow who does it five times is hidden for the session. Posts stuffed with more than ten hashtags are hidden too.', modReportsToggle: 'Fold posts reported by people you follow', modReportsHelp: 'Their reports (NIP-56) are read from the relays. A flagged post is shown collapsed, with who flagged it; tap to see it anyway.', postBlocked: 'Blocked', zapSending: 'Sending your zap…', msgReply: 'Reply', mediaOpenPost: 'Open the post', 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.", addrLoading: 'Fetching…', addrNotFound: "Couldn't find that on the relays.", addrArticle: 'Article', addrRead: '{min} min read', addrList: 'A list by {name}', msgSignerOff: "Your nostr signer isn't connected, so you can't post as yourself yet.", msgAmberDms: 'Signed in with Amber, messages sent to you can’t be opened here, only ones you sent from this device.', msgAmberConnect: 'Connect Amber', postRepostedBy: '{name} reposted', gifBtnTitle: 'Add a GIF', gifSearchPh: 'Search GIFs', gifFrom: 'GIFs from', gifNone: 'No GIFs found.', gifDown: 'GIF search isn’t available right now.', gifLimited: 'Too many searches. Try again in a minute.', msgReconnect: 'Reconnect', msgDmsTitle: 'Messages', msgCommunitiesTitle: 'Communities', msgNewDm: 'New message', msgNoDms: 'No conversations yet.', msgShowAllDms: 'Show all {n} conversations', msgShowFewerDms: 'Show fewer', msgShowAllCommunities: 'Show all {n} communities', msgNoDmsYet: 'No messages yet. Say hi!', msgDmPlaceholder: 'Message', msgDmEncrypted: 'Private message · end-to-end encrypted', msgNewGroup: 'New group', msgGroupName: 'Group name', msgGroupPeople: 'Add people by name or npub…', msgGroupCreate: 'Create group', msgGroupNeedPeople: 'Add at least one person', msgGroupChecking: 'checking…', msgGroupCantJoin: "can't join group chats yet", msgGroupSomeCant: "{names} can't join group chats yet", msgGroupInvite: '{name} invited you to {group}', msgGroupUnnamed: 'Group chat', msgGroupDecline: 'Decline', msgGroupInfo: 'Private group · end-to-end encrypted', msgGroupLeave: 'Leave group', msgGroupRemove: 'Remove', msgGroupRename: 'Rename', msgGroupGone: 'You are no longer in this group', msgGroupAttachment: 'Attachment', msgGroupFailed: "Couldn't update the group. Try again", msgGroupSetup: 'Your signer is needed once to set up group chats', msgGroupProto: 'Group type', msgGroupProtoWn: 'Marmot (MLS)', msgGroupProtoCd: 'Cordn', msgGroupProtoCdHint: 'Cordn groups work with Amethyst and the Cordn app; messages travel through a coordinator server that cannot read them.', msgCordnInfo: 'Cordn group · end-to-end encrypted · delivered by a coordinator', msgCordnNoFiles: 'Attachments are not available in Cordn groups yet', msgRouteTitle: 'Send messages by', msgRouteAuto: 'Automatic', msgRouteAutoHint: 'Reply the way they last wrote', msgRouteWn: 'Marmot (MLS)', msgRouteWnHint: 'Encrypted with forward secrecy · works with White Noise', msgRouteDm: 'Nostr DM', msgRouteDmHint: 'Reaches any Nostr app that reads private messages', msgSysAdded: '{a} added {b}', msgSysJoined: '{b} joined', msgSysRemoved: '{a} removed {b}', msgSysLeft: '{b} left', msgSysAdminAdded: '{b} is now an admin', msgSysAdminRemoved: '{b} is no longer an admin', msgSysRenamed: '{a} named the group {name}', msgSysDisbanded: '{a} ended the group', 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.", spendingRenewingPayment: 'Your Spending coins are renewing. You can pay once the renewal finishes.', 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: 'Unilateral 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', arkCoinsNone: 'No coins in Spending yet.', 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', noteForeignKind: 'A kind {kind} event this app doesn’t show.', zapRowText: 'zapped {n} sats', pollVoter: '{n} vote', pollVoters: '{n} votes', pollMulti: 'Pick any', pollEnded: 'Poll closed', pollEndsAt: 'Closes {when}', arkExpiredDust: '{amount} has expired and is too small to renew. You can forget it from the Manage page.', arkCoinForget: 'Forget', arkCoinForgetTitle: 'Give this coin up: it has expired and is too small to renew or exit.', arkCoinForgotten: 'Coin forgotten', arkCoinsRenewing: 'renewing', arkCoinsIntroRenewing: 'Your Spending balance of {total} is being renewed: the coins below are in a round and come back as one fresh coin. Each can be pulled on-chain without the server’s help, at a cost that grows with the coin’s history.', arkRenewingTitle: 'Renewal in progress', arkRenewingLine: '{amount} sats → one fresh coin{fee}', arkRenewElsewhere: 'Started on another device, which finishes it the next time it’s open.', arkRenewingFee: ', fee {fee} sats', arkRenewStageQueued: 'Joining the next round. The wallet submits it on its next sync.', arkRenewStageRound: 'In a round. The fresh coin arrives once the round’s transaction has a confirmation, about a block from now, the next time this wallet syncs.', arkRenewStageFinishing: 'Finishing: signing the fresh coin.', arkRenewStageRetry: 'The last check with the server didn’t go through; the wallet keeps trying.', arkRenewingHistory: 'Renewing', arkRenewingOpen: 'See the coins', arkCoinsExitTitle: 'Unilateral exit', arkCoinsCoopTitle: 'Co-operative exit', arkOffboardBadAddress: 'Enter a valid on-chain address for this network.', arkCoinsExitBtn: 'Exit now', arkExitConfirmTitle: 'Start a unilateral exit?', arkExitConfirmBody: 'This brings your Spending coins to Savings on-chain without the server, by publishing each coin’s transaction history. Nothing needs the server’s cooperation (that is the point), but it is slow and it costs mining fees.', arkExitConfirmCoins: 'Coins', arkExitConfirmAmount: 'Amount', arkExitConfirmFee: 'Mining fees (about, paid from Savings)', arkExitConfirmSavings: 'Savings available for fees', arkExitConfirmWait: 'The funds arrive in Savings only once each coin’s timelock has run out, hours to days. An exit can’t be stopped once it has started.', arkExitConfirmGo: 'Start the 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 should the coinos server ever stop 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:', arkCoinsRenewDescBase: 'Your wallet renews coins automatically once they are close to expiring, when renewal is cheapest. Renewing early is an option if you would rather pay a little more now to keep your exit cost low should the coinos server ever stop 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. Every renewal costs a flat {base} toward the round’s on-chain fee, plus a share that 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', arkOffboardQuote: 'Fee about {fee} sats right now ({chain} mining + {service} coinos), so it needs at least {min} sats.', arkOffboardQuoting: 'Checking the fee…', arkOffboardBelowMin: 'Your {n} sats are below the {min}-sat minimum for sending on-chain right now. Send them over Lightning instead: no mining fees.', arkOffboardAmtBelowMin: 'Sending on-chain needs at least {min} sats right now (the fee is about {fee}).', 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!', footerSupport: 'Show your support', 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.', profTabPosts: 'Posts', profTabReplies: 'Replies', profRepliesNone: 'No recent replies 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', msgPopularCommunities: 'Popular communities', msgBrowseCommunities: 'Browse all', msgHubNoSuggestions: 'No new communities to suggest right now.', 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', msgLeaveConfirmShort: 'Leave?', 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, so 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', securityTitle: 'Security', 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.', 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.', arkBoardPaused: "Moving money from Savings into Spending is paused for now while the Ark server gets a security fix. Everything else works as usual.", 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!', arkZapNoPostZap: 'Can’t zap this post: its author has no Lightning address or Ark address.', arkZapLnDown: 'Can’t pay Lightning right now: Spending isn’t connected. Try again in a moment.', 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', arkServerTitle: 'Ark server', arkServerDesc: 'Spending runs on an Ark server. coinos runs one; Second runs another; or point at your own. Coins are held per server. Switch back any time to reach what a server holds.', arkServerCustomUrl: 'Ark server URL (https://…)', arkServerCustomEsplora: 'Esplora API URL for that server', arkServerCustomNeeded: 'Enter both the Ark server and its Esplora URL first', arkServerSwitchWarn: '{amount} sats are held with the current server. They stay there (switch back to reach them) but can’t be spent from the new one. To move them, send or exit them first.', arkServerSwitchGo: 'Switch server', arkCmpShow: 'Compare the servers', arkCmpHide: 'Hide the comparison', arkCmpRunBy: 'Run by', arkCmpRunByCoinos: 'coinos', arkCmpRunBySecond: 'Second, the makers of Ark', arkCmpRunByOther: 'someone else', arkCmpLifetime: 'Coin lifetime before renewal', arkCmpRounds: 'Rounds', arkCmpEvery: 'every {n} min', arkCmpRenew: 'Renewal fee', arkCmpLnSend: 'Lightning send fee', arkCmpLnRecv: 'Lightning receive fee', arkCmpExit: 'Cooperative exit fee', arkCmpBoard: 'Boarding', arkCmpBoardVal: 'min {min} sats, {n} confirmations', arkCmpMaxCoin: 'Largest coin', arkCmpFree: 'free', arkCmpFreeNear: 'free within {span} of expiry', arkCmpMin: 'min {n} sats', arkCmpNote: 'Fees graded by expiry cost more the earlier a coin is renewed or exited. Figures come from each server’s own published info, read just now. Either server pays Lightning invoices and takes Lightning payments to your coinos name.', arkServerSwitched: 'Ark server switched', arkOtherServerReceipt: '{amount} sats arrived with {server}. Switch to that server in Settings → Advanced to spend them.', arkForeignBip21: 'Their Ark address is on a different Ark server, so the on-chain address is paid instead.', arkForeignServer: 'That Ark address belongs to a different Ark server, so it can’t be paid from here directly. Ask for a Lightning invoice, or pay their coinos name or npub. Those fall back to Lightning by themselves.', arkLnFromSavingsTitle: 'Lightning pays from Spending', arkLnFromSavingsBody: 'Lightning payments leave your Spending balance, not Savings. Spending holds {have}.', arkLnFromSavingsShort: 'Spending can’t cover this one. Move funds over from Savings first. That takes an on-chain confirmation, so ask for a fresh invoice afterwards.', arkLnUseSpending: 'Switch to Spending and continue', arkLnMoveFirst: 'Move Savings into Spending first', arkLnFee: 'Fee', arkLnPaying: 'Paying invoice…', arkLnRetrying: 'Route failed. Retrying with a bigger fee budget ({n}/{of})…', arkLnPayFailed: 'Payment failed', arkRenewConfirmTitle: "Renew these coins?", arkRenewConfirmBody: "Your wallet renews coins by itself, for free, when they are close to expiring. Renewing now is optional.", arkRenewConfirmFee: "Renewal fee", arkRenewConfirmLeft: "Left to spend meanwhile", arkRenewConfirmAll: "This is your whole Spending balance. You won’t be able to pay or send from Spending until the renewal confirms.", arkRenewConfirmWait: "Coins being renewed can’t be spent until the round’s transaction confirms on the Bitcoin network: usually about ten minutes, sometimes an hour or more. A renewal can’t be cancelled once it has started.", arkRenewViewTx: "View the round’s transaction", arkLnRenewing: "Nothing was sent. {sats} sats are being renewed and can’t be spent until the renewal confirms on the Bitcoin network: usually about ten minutes, sometimes an hour or more. Try again then.", arkLnShort: "Not enough in Spending: this payment needs {need} sats with its fee, and {have} are available.", 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', onbHaveSeed: 'Have an existing seed or entropy to import?', nlGooglePrev: 'Signed in with Google here before? Use the previous sign-in server', onbCreateInstead: 'Create a new wallet instead', 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', onbMigrateDoor: 'Migrate a coinos account', onbMigrateIntro: "Moving from the original coinos.io? Your balance and username come with you. First, set up the wallet they'll move into and write down its twelve words: they're yours alone, and the only way back in. Then we'll take you to coinos.io to finish the move.", onbMigrateCreate: 'Create my new wallet', 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 cover and an avatar, or upload your own. You can change them anytime.', onbCover: 'Cover image', onbCoverPrev: 'Previous cover', onbCoverNext: 'Next cover', onbCoverUpload: 'Upload a cover', onbCoverYours: 'Your photo', onbUpload: 'Upload a photo', onbUploadFailed: 'Upload failed. Try another picture', onbProfileSkipped: 'Couldn’t publish your profile. Set it later from Edit profile', onbContinue: 'Continue', onbSkipAvatar: 'Continue', 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', nlAmber: 'Sign in with Amber (Android)', nlAmberWait: 'Waiting for your signer app… approve there, then come back here.', nlAmberPasteHint: 'The answer is read from the clipboard when you return. If nothing happens, paste it here:', nlAmberPastePh: 'Paste the signer’s answer', nlAmberPasteBtn: 'Use it', nlAmberBadPaste: 'That doesn’t look like the answer to this request.', 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', nlAmberUpgradeTitle: 'Read your messages', nlAmberUpgradeBody: 'Amber can only open messages here when it’s connected as a remote signer. Tap below, approve in Amber (allow decrypting), and your messages will load. Signing works as before.', 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/flash. 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', namesRule: 'Letters and numbers only', 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: 'Network, rarities, rescues', settingsPosDesc: 'Till mode, tips and the /pos link', 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. Change it any time by holding ⚡, or in Settings → Payments.', zapSetupSave: 'Save and zap', zapSettingsDesc: 'This is what one tap on ⚡ sends: no form, no confirmation. Tap again while one is still on its way to send another.', zapAmountSaved: 'One tap now zaps {n} sats', zapSoundToggle: 'Play a sound when you 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, address, or #topic…', 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', notifZap: 'Zaps on your posts', notifReaction: 'Reactions to your posts', 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.', sendNameHint: 'Sent instantly, no 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', 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(assetUrl('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; }