I18n automation (#77)

This commit is contained in:
KraXen72
2024-06-05 11:18:36 +02:00
committed by GitHub
parent 1a63f6f17d
commit 9932cb52d4
47 changed files with 2349 additions and 1806 deletions
-1
View File
@@ -1,6 +1,5 @@
# App specific
.env
.vscode/
google-services.json
# OSX
#
+5
View File
@@ -0,0 +1,5 @@
{
"recommendations": [
"inlang.vs-code-extension"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"[javascript][typescript][javascriptreact][typescriptreact]": {
"editor.insertSpaces": true,
"editor.tabSize": 2
}
}
+16
View File
@@ -0,0 +1,16 @@
const { exec } = require('child_process');
exec('tsc --noEmit', (error, stdout, stderr) => {
if (error) {
const output = stdout;
const filteredErrors = output.split('\n').filter(line => line.includes('TxKeyPath'));
if (filteredErrors.length > 0) {
console.log(filteredErrors.join('\n'));
} else {
console.log('No errors containing TxKeyPath found.');
}
// process.exit(1); // Ensure the script returns an error code if there were any errors
} else {
console.log('No TypeScript errors found.');
}
});
+27
View File
@@ -0,0 +1,27 @@
{
"$schema": "https://inlang.com/schema/project-settings",
"sourceLanguageTag": "en",
"languageTags": [
"en",
"sk"
],
"modules": [
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
"https://cdn.jsdelivr.net/gh/KraXen72/inlang-t-function-jsx-hybrid-matcher/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/plugin-json@latest/dist/index.js"
],
"plugin.inlang.json": {
"pathPattern": "./src/i18n_messages/{languageTag}.json",
"variableReferencePattern": [
"%{",
"}"
]
},
"plugin.minibits.inlangmatcher": {
"preferredTfuncName": "translate",
"recognizedTfuncNames": [
"translate"
]
}
}
+1
View File
@@ -13,6 +13,7 @@
"start": "npx react-native start",
"test": "jest",
"adb": "adb devices && adb reverse tcp:9090 tcp:9090 && adb reverse tcp:3000 tcp:3000 && adb reverse tcp:9001 tcp:9001 && adb reverse tcp:8081 tcp:8081",
"i18n:missing": "node __tests__/missingTranslations.js",
"postinstall": "patch-package --exclude 'nothing'"
},
"dependencies": {
-325
View File
@@ -1,325 +0,0 @@
const en = {
common: {
ok: "OK",
cancel: "Cancel",
close: "Close",
back: "Back",
paste: "Paste",
scan: "Scan",
copy: "Copy",
copyFailParam: "Couldn't copy %{param}",
confirm: "Confirm",
save: "Save",
add: "Add",
success: "Success!",
offline: "Offline",
qr: "QR Code",
sats: "SATS",
notCreated: 'Not yet created',
resetDefault: 'Reset to default',
share: "Share",
},
paymentCommon: {
pay: "Pay",
payMe: 'Pay me',
payFromWallet: 'Pay from wallet',
receiveInPerson: "Receive in person"
},
transactionCommon: {
from: "From %{sender}",
receivedFrom: "Received from %{sender}",
sentTo: "Sent to %{receiver}",
paidTo: "Paid to %{receiver}",
youReceived: 'You received',
youSent: 'You sent',
youPaid: 'You paid',
unknown: 'Unknown transaction',
status: {
completedFee: 'Completed %{fee}',
draft: 'Draft',
error: 'Error',
pending: 'Pending',
prepared: 'Prepared',
reverted: 'Reverted',
blocked: 'Blocked',
expired: 'Expired'
},
tapToRedeem: 'Tap to redeem',
redeemOnline: 'Redeem online',
feePaid: 'Paid fee'
},
welcomeScreen: {
page1: {
heading: "Welcome",
intro: "Minibits is an ecash and Lightning wallet with a focus on performance and usability. Ecash is a bearer token issued by custodians known as mints.",
bullet1: "Minibits follows the Cashu protocol, where mints back ecash with Bitcoin.",
bullet2: "Ecash is issued or exchanged back to Bitcoin instantly through Lightning payments.",
bullet3: "Ecash tokens are stored on-device; mints do not keep ledger or wallet balances.",
final: "No Watson, there is no blockchain."
},
page2: {
heading: "Why Minibits?",
intro: "Minibits' aim is to research how Lightning and ecash can be integrated into a seamless and instant experience that can work at scale and still provide good privacy.",
bullet1: "Minibits provides sharable wallet identifiers using NOSTR addresses. Like account numbers, just better.",
bullet2: "Try in-person sends while devices are offline - ecash settles when back online (Proof of concept).",
bullet3: "You can opt-in for storage encryption and biometric authentication.",
final: "Minibits is free and open-source software; find us on Github for the roadmap and contributions."
},
page3: {
heading: "Do not forget",
intro: "Both the Cashu protocol and the Minibits wallet are still experimental, and by using them, you accept known and unknown risks.",
bullet1: "Mints are, by design, custodial services. Run your own or use them only for research and testing purposes.",
bullet2: "Ecash is stored on your device, so the loss of the device means the loss of ecash. Remote backup protocol is still under research.",
bullet3: "Minibits provides its own mint that you can use for testing purposes with small amounts. It is operated on a best-effort basis and without any guarantees.",
final: "Now, let's move some ecash!",
go: "Let's go!"
}
},
walletScreen: {
fund: "Fund wallet",
withdraw: "Withdraw",
send: "Send",
receive: "Receive",
topUpWallet: "Top up wallet",
topUpWalletSubText: "Top up your balance by paying Bitcoin lightning invoice from another wallet",
transferFromWallet: "Transfer from wallet",
transferFromWalletSubText: "Debit your balance by paying Bitcoin lightning invoice from this wallet",
feeBadge: {
final: "+ final fee %{fee} %{code}",
upto: "+ fee up to %{fee} %{code}"
}
},
receiveScreen: {
title: "Receive",
paste: "Paste ecash",
receive: "Receive",
receiveOffline: "Receive offline",
newMintsAdded: {
one: "{{count}} mint has been added to your wallet",
other: "{{count}} mints have been added to your wallet",
},
toReceive: "Amount to receive",
received: "Amount received",
sharePaymentRequest: "Send payment request",
sharePaymentRequestDescription: "Send payment request to one of your contacts, so that the payer can pay you exact amount.",
scanToReceive: "Scan or paste to receive",
scanToReceiveDescription: "Scan or paste ecash token or lnurl withdraw link to receive into your wallet.",
pasteToReceive: "Paste",
pasteoReceiveDescription: "Paste ecash token to receive it into your wallet.",
showOrShareInvoice: "Share lightning invoice",
showOrShareInvoiceDescription: "Present or share lightning invoice for an amount you want to receive." ,
scanWithdrawalLink: "Scan withdrawal link"
},
transferScreen: {
},
topupScreen: {
},
sendScreen: {
title: "Send",
sendToContact: "Send to contact",
sendToContactDescription: "Send your ecash to one of your contacts stored in your contact list.",
scanToPay: "Scan to pay",
scanToPayDescription: "Scan Lightning invoice, LNURL pay code or Lightning address to pay.",
pasteToSend: "Paste",
pasteToSendDescription: "Paste lightning invoice from clipboard to pay it from your wallet.",
showOrShareToken: "Share ecash",
showOrShareTokenDescription: "Present or share ecash token for an amount you want to send.",
},
tranDetailScreen: {
amount: "Amount",
lightningFee: "Lightning network fee",
memoFromSender: "Memo from sender",
sentFrom: "Sent from",
sentTo: "Sent to",
type: "Type",
status: "Status",
balanceAfter: "Balance after this transaction",
createdAt: "Created at",
id: "ID",
memoToReceiver: "Memo to receiver",
revert: "Revert",
claim: "Claim",
memoFromInvoice: "Memo from invoice",
paidFrom: "Paid from",
receivedTo: "Received to",
topupTo: "Topup to",
trasferredTo: "Paid to",
invoice: "Lightning invoice to pay",
receiveOfflineComplete: "Redeem to wallet",
isOffline: "Redeem online"
},
contactsScreen: {
new: "New",
scan: "Scan",
newTitle: "Add new contact",
getInvoice: "Get invoice",
ownName: {
donationSuccess: "Thank you! Donation for %{receiver} has been successfully paid.",
tooShort: 'Write your wallet profile name to the text box, use min 2 characters.',
illegalChar: 'Do not use . or - characters at the beginning or the end of name.',
profileExists: 'This wallet name is already in use, choose another one.',
chooseOwnName: 'Choose your own name',
chooseOwnNameFooter: 'Use lowercase letters, numbers and .-_',
available: "%{name} is available!",
payToGetOwnName: "Pay the following lightning invoice and get your %{name} wallet name.",
ctaPay: "Pay from wallet",
insufficient: 'Your wallet balance is not enough to pay this invoice amount but you can still pay it from another wallet.',
betaWarning: 'Please accept this is an early beta software. Your data can still be lost due to a bug or unexpected data loss.'
},
privateContacts: {
selectSendPaymentRequest: 'Select contact to send your payment request to.',
selectSendToken: 'Select contact to send your ecash to.',
selectSendLnURL: 'Select contact to send Lightning payment to.',
saveNewFormat: "Please enter a wallet address in name@domain.xyz format",
profileNotFound: "Profile name %{name} could not be found. Make sure the name is correct.",
noLightningAddress: 'This contact does not have a Lightning address, send ecash instead.',
explainerText: "Private contacts",
explainerSubText: "Add other Minibits users as your private contacts. Every user gets sharable @minibits.cash wallet address. You can pay privately to your contacts anytime even if they are offline.",
switchName: "Switch your wallet name and picture?",
switchNameSubText: 'Get cooler wallet name or profile picture. Select from an array of random names and images or opt for your own @minibits.cash wallet name.',
bottomModal: "Private contacts are unique identifiers of other Minibits wallets. You can use them to send or request ecash and you can safely share your own with others.",
domainMinibits: "Use minibits.cash domain",
domainExternal: "Use another domain"
},
publicContacts: {
npubPasteError: 'Copy your NPUB key first, then paste',
relayurlPasteError: 'Copy your relay URL key first, then paste',
relayExists: 'Relay already exists.',
missingLightningAddress: 'This contact does not have a Lightning address, send ecash instead.',
nostrTip: 'Tip the people you follow',
nostrTipSubText: 'Add your NOSTR social network public key (npub) and tip or donate to your favourite people and projects directly from the minibits wallet.',
nostrSetPublicKey: 'Set your public key',
nostrSetPublicKeySubText: 'Add or change your NOSTR social network public key (npub).',
nostrSetRelay: 'Set relay',
nostrSetRelaySubText: 'Add or change your own relay if your profile and follows are not hosted on the default relays.',
nostrRemovePub: 'Remove your public key',
nostrRemovePubSubText: 'Remove your npub key and stop loading public contacts.',
addNpub: 'Add your npub key',
pasteDemoKey: 'Paste demo key',
setOwnRelay: 'Set your own relay'
},
randomName: {
selectOneOfUsernames: 'Select one of the usernames'
}
},
paymentRequestScreen: {
incoming: {
noRequests: 'There are no incoming payment requests to be paid or they have already expired.'
},
outgoing: {
noRequests: 'There are no outgoing payment requests to be paid or they have already expired.',
invoiceShared: 'Lightning invoice has been shared, waiting to be paid by receiver.',
sharingCancelled: 'Sharing cancelled',
scanAndPay: 'Scan and pay to top-up'
},
// might move this to common later if it gets used in other screens
listItem: {
expires: "Expires",
expired: "Expired",
requestPaymentSuccess: 'Request has been paid'
}
},
profileScreen: {
changeAvatar: "Change picture",
changeAvatarSubtext: "Select one from randomly generated pictures.",
changeWalletaddress: "Change wallet address",
changeWalletaddressSubtext: "Change unique wallet address. Select one of randomly generated or choose your own.",
},
settingsScreen: {
title: "Settings",
manageMints: "Manage mints",
preferredUnit: "Preferred unit",
mintsCount: {
one: "{{count}} mint",
other: "{{count}} mints",
},
go: "Let's go!",
backupRecovery: "Backup and recovery",
security: "Security",
privacy: "Privacy",
devOptions: "Developer options",
update: "Update manager"
},
mintsScreen: {
addMintUrl: "Add mint URL",
mintUrl: "Mint URL",
mintUrlHint: "Provide the URL of a trusted mint",
invalidUrl: "This is not a valid URL",
mintExists: "This mint already exists",
addMint: "Add mint",
mintAdded: "The mint has been added",
mintInfo: "Mint information page",
rename: "Rename mint",
setColor: "Change the mint's icon color",
copy: "Copy mint URL",
removeMint: "Remove mint",
blockMint: "Block receiving from this mint",
unblockMint: "Unblock receiving from this mint",
mintRemoved: "The mint has been removed",
mintBlocked: "The mint has been blocked. The app will not receive ecash from this mint.",
mintUnblocked: "The mint has been unblocked. You can now receive ecash from this mint."
},
backupScreen: {
localBackup: "Local backup",
localBackupDescription: "Local backup stores a copy of all your ecash in the local database. Ecash is never deleted unless you switch off the backup. Please note that this backup is not encrypted.",
remoteBackup: "Seed backup",
remoteBackupDescription: "Mnemonic phrase encodes your seed and allows you to recover your ecash balance in case of device loss.",
recoveryTool: "Local recovery tool",
recoveryToolDescription: "Show ecash backed up in the local database and attempt to recover unspent ones into the wallet in case the wallet storage gets corrupted or a transaction fails due to an unexpected error.",
removeSpentCoins: "Remove spent ecash",
removeSpentCoinsDescription: "In case of a SEND or TRANSFER transaction error, spent ecash may remain in your wallet. This blocks further transactions with the mint, making the wallet unusable. This tool removes spent ecash from the wallet."
},
securityScreen: {
encryptStorage: "Encrypt storage",
encryptStorageDescription: "Encrypt the storage that stores your ecash with the secret key generated on your device. Experimental, not recommended for every day use.",
biometry: "Biometric authentication",
biometryAvailable: "Biometric authentication is now required for Minibits to start and unlock the encryption key.",
biometryNone: "You have not setup biometric authentication or your device does not support it.",
},
privacyScreen: {
torDaemon: "Use Tor network",
torDaemonDescription: "Allow connections with mints through Tor .onion addresses.",
torStatus: "Tor status",
logger: "Enable logging",
loggerDescription: "Log anonymous error information in order to identify and fix wallet bugs during beta testing. Switch off for best privacy.",
},
updateScreen: {
updateAvailable: "Update is available",
nativeUpdateAvailableDesc: "There is a new version of Minibits Wallet in the Google Play Store.",
gotoPlayStore: 'Visit Play Store',
updateNow: 'Update and restart',
updateAvailableDesc: "There is a new Over-the-Air update of Minibits Wallet ready to download and install.",
updateNew: "What's new?",
updateSize: "Download size",
updateNotAvailable: 'Nothing to update',
updateNotAvailableDesc: 'Good, you are running the latest version of the app.'
},
developerScreen: {
title: "Developer options",
transactions: "Sync recent transactions",
transactionsDescription: "There are {{count}} recent transactions cached in the app's fast storage. Resetting will reload them from the app's database. Do only during development or testing.",
reset: "Factory reset",
resetDescription: "This will wipe out all local data from storage and from the local database. Do only during development or testing.",
go: "Let's go!",
devOptions: "Developer options",
info: "About Minibits wallet",
logLevel: "Log level"
},
errorScreen: {
title: "Something went wrong!",
friendlySubtitle:
"Unfortunately, the Minibits app encountered an unexpected error. Please contact us at support@minibits.cash or raise an issue on Github.",
reset: "RESET APP",
},
tabNavigator: {
walletLabel: "Wallet",
contactsLabel: "Contacts",
settingsLabel: "Settings",
},
};
export default en;
export type Translations = typeof en;
+3 -4
View File
@@ -1,12 +1,11 @@
// import * as Localization from "expo-localization"
// import { I18nManager } from "react-native"
import { I18n } from "i18n-js"
import { I18nManager } from "react-native"
// if English isn't your default language, move Translations to the appropriate language file.
import en, { Translations } from "./en"
import en from "../i18n_messages/en.json"
type Translations = typeof en;
export const i18n = new I18n({'en-US': en})
// i18n.fallbacks = true
+1 -1
View File
@@ -12,7 +12,7 @@ import {TxKeyPath} from './i18n'
* @example
* Translations:
* ```ts
* // en.ts
* // en.json
* {
* "heading": "Welcome to the app!",
* "buttonText": "Donate",
+612
View File
@@ -0,0 +1,612 @@
{
"addMints": "Add mints",
"amount": {
"requested": "Requested amount",
"topup": "Topup amount",
"withdraw": "Withdraw amount"
},
"attention": "Attention!",
"backupScreen": {
"deletedSuccess": "Your backup of minibits tokens has been deleted. New tokens will NOT be backed up.",
"invalidMnemonicError": "Corrupted or invalid mnemonic. Please move funds and reinstall this wallet.",
"localBackup": "Local backup",
"localBackupDescription": "Local backup stores a copy of all your ecash in the local database. Ecash is never deleted unless you switch off the backup. Please note that this backup is not encrypted.",
"missingMnemonicError": "Missing mnemonic phrase.",
"missingMnemonicOrSeedError": "Missing mnemonic or seed.",
"mnemonicDesc": "This 12 word sequence represents your seed and allows you to recover your ecash balance in case of device loss. Keep them in safe place.",
"mnemonicTitle": "Your mnemonic phrase",
"recoveryTool": "Local recovery tool",
"recoveryToolDescription": "Show ecash backed up in the local database and attempt to recover unspent ones into the wallet in case the wallet storage gets corrupted or a transaction fails due to an unexpected error.",
"remoteBackupDescription": "Mnemonic phrase encodes your seed and allows you to recover your ecash balance in case of device loss.",
"removeSpentCoins": "Remove spent ecash",
"removeSpentCoinsDescription": "In case of a SEND or TRANSFER transaction error, spent ecash may remain in your wallet. This blocks further transactions with the mint, making the wallet unusable. This tool removes spent ecash from the wallet.",
"result": "%{proofCount} ecash proofs, %{amount} in total were removed from the wallet.",
"seedBackup": "Seed backup",
"success": "Your minibits tokens were backed up to local database. New tokens will be backed up automatically.",
"remoteBackup": "Seed backup"
},
"backupWillOverwriteBalanceWarning": "Your wallet has non zero-balance. If you continue, existing ecash will be deleted and replaced by backup.",
"checkAndSync": "Check and sync",
"checkAndSyncDesc": "Checks that contact name is still linked to the same pubkey and updates picture if it was changed.",
"common": {
"add": "Add",
"back": "Back",
"cancel": "Cancel",
"change": "Change",
"close": "Close",
"complete": "Complete",
"confirm": "Confirm",
"confirmAlertTitle": "Confirmation",
"confirmCreateInvoice": "Create invoice",
"connectedParam": "Connected: %{param}",
"copy": "Copy",
"copyFailParam": "Couldn't copy %{param}",
"create": "Create",
"disabled": "Disabled",
"enabled": "Enabled",
"hideMore": "Collapse",
"import": "Import",
"noDescPlaceholder": "No description",
"notCreated": "Not yet created",
"off": "off",
"offline": "Offline",
"offlinePretty": "Your device is currently offline",
"ok": "OK",
"on": "on",
"paste": "Paste",
"pasteEcash": "Paste ecash",
"pasteEcashToken": "Paste ecash token",
"qr": "QR Code",
"reconnect": "Reconnect",
"reset": "Reset",
"resetDefault": "Reset to default",
"sats": "SATS",
"save": "Save",
"saveFailParam": "Failed to save %{param}",
"scan": "Scan",
"sendAsEcash": "Send as ecash",
"share": "Share",
"showMore": "Show more",
"success": "Success!"
},
"confirmBackupRecovery": "Do you really want to recover ecash from the backup?",
"confirmMintRemoval": "Do you really want to remove %{hostname} - %{shortname} from the wallet?",
"contacts": "Contacts",
"contactsScreen": {
"getInvoice": "Get invoice",
"new": "New",
"newTitle": "Add new contact",
"ownName": {
"available": "%{name} is available!",
"betaWarning": "Please accept this is an early beta software. Your data can still be lost due to a bug or unexpected data loss.",
"chooseOwnName": "Choose your own name",
"chooseOwnNameFooter": "Use lowercase letters, numbers and .-_",
"ctaPay": "Pay from wallet",
"donationSuccess": "Thank you! Donation for %{receiver} has been successfully paid.",
"illegalChar": "Do not use . or - characters at the beginning or the end of name.",
"insufficient": "Your wallet balance is not enough to pay this invoice amount but you can still pay it from another wallet.",
"payToGetOwnName": "Pay the following lightning invoice and get your %{name} wallet name.",
"profileExists": "This wallet name is already in use, choose another one.",
"tooShort": "Please use at least 2 characters for your name"
},
"privateContacts": {
"bottomModal": "Private contacts are unique identifiers of other Minibits wallets. You can use them to send or request ecash and you can safely share your own with others.",
"domainExternal": "Use another domain",
"domainMinibits": "Use minibits.cash domain",
"explainerSubText": "Add other Minibits users as your private contacts. Every user gets sharable @minibits.cash wallet address. You can pay privately to your contacts anytime even if they are offline.",
"explainerText": "Private contacts",
"profileNotFound": "Profile name %{name} could not be found. Make sure the name is correct.",
"saveNewFormat": "Please enter a wallet address in name@domain.xyz format",
"selectSendLnURL": "Select contact to send Lightning payment to.",
"selectSendPaymentRequest": "Select contact to send your payment request to.",
"selectSendToken": "Select contact to send your ecash to.",
"switchName": "Switch your wallet name and picture?",
"switchNameSubText": "Get cooler wallet name or profile picture. Select from an array of random names and images or opt for your own @minibits.cash wallet name."
},
"publicContacts": {
"addNpub": "Add your npub key",
"npubPasteError": "Copy your NPUB key before pasting",
"pasteDemoKey": "Paste demo key",
"setOwnRelay": "Set your own relay"
},
"randomName": {
"selectOneOfUsernames": "Select one of the usernames"
},
"scan": "Scan"
},
"copyAsEncodedTokens": "Copy as encoded tokens",
"copyContactPublicKey": "Copy contact's public key",
"copyMnemonicBackupWorkaround": "To apply backup to your existing funds, send your entire balance to yourself. Otherwise, only funds from this point onward can be restored.",
"copyProofs": "text=\"Copy proofs\"",
"decodedMissingCurrencyUnit": "Currency unit is missing in the received token. Wallet will assume token amount is in bitcoin $%{unit}. Do not continue if you are not sure this is correct.",
"deleteContact": "Delete contact",
"deleteContactDesc": "Remove this contact from your wallet.",
"derivingSeedStatus": "Deriving seed, this takes a while...",
"developerScreen": {
"devOptions": "Developer options",
"go": "Let's go!",
"info": "About Minibits wallet",
"logLevel": "Log level",
"reset": "Factory reset",
"resetDescription": "This will wipe out all local data from storage and from the local database. Do only during development or testing.",
"title": "Developer options",
"transactions": "Sync recent transactions",
"transactionsDescription": "There are {{count}} recent transactions cached in the app's fast storage. Resetting will reload them from the app's database. Do only during development or testing."
},
"errorScreen": {
"friendlySubtitle": "Unfortunately, the Minibits app encountered an unexpected error. Please contact us at support@minibits.cash or raise an issue on Github.",
"reset": "RESET APP",
"title": "Something went wrong!"
},
"factoryResetSuccess": "Factory reset completed, please restart immediately.",
"factoryResetUserConfirmDesc": "All application data will be deleted. Are you sure you want to perform a factory reset?",
"increaseRecoveryIndexes": "Increase recovery indexes",
"increaseRecoveryIndexesDesc": "After incomplete recovery from seed or other rare issue, you may encounter 'duplicate outputs' error when trying to send. This resets your recovery indexes to higher value in order to resolve the issue.",
"invalidRelayUrl": "Invalid relay URL.",
"lightningAddress": "Lightning address",
"lightningInvoiceSharedWaiting": "Lightning invoice has been shared, waiting to be paid by receiver.",
"lightningPayScreen": {
"onPasteEmptyClipboard": "First copy Lightning invoice, address or pay code to pay to. Then paste."
},
"loadingPublicInfo": "Loading public info",
"localBackupDisabled": "Local backup is off",
"localBackupEnabled": "Local backup is on",
"loglevelDebugDesc": "Log details, set only if testing or on request by support.",
"loglevelErrorDesc": "Log errors and crashes only, without private data.",
"loglevelInfoDesc": "Log anonymous usage, without private data.",
"manageMints": "Manage mints",
"mintInfoHeading": "Mint info",
"mintSettingsUpdated": "Updated mint settings",
"mintsScreen": {
"addMint": "Add mint",
"addMintMinibits": "Add Minibits mint",
"addMintUrl": "Add mint URL",
"blockMint": "Block receiving from this mint",
"copy": "Copy mint URL",
"invalidUrl": "This is not a valid URL",
"mintAdded": "The mint has been added",
"mintBlocked": "The mint has been blocked. The app will not receive ecash from this mint.",
"mintExists": "This mint already exists",
"mintInfo": "Mint information page",
"mintRemoved": "The mint has been removed",
"mintUnblocked": "The mint has been unblocked. You can now receive ecash from this mint.",
"mintUrl": "Mint URL",
"mintUrlHint": "Provide the URL of a trusted mint",
"refreshMintSettings": "Refresh mint settings",
"removeMint": "Remove mint",
"rename": "Rename mint",
"setColor": "Change the mint's icon color",
"unblockMint": "Unblock receiving from this mint"
},
"missingEcashTokenToReceiveError": "Missing ecash token to receive.",
"missingLNURLParamsError": "Missing LNURL params.",
"missingMintsForProofsUserMessage": "Please add all mints belonging to your proofs to the wallet and try again.",
"missingMintUnitRouteParamsError": "Missing mint unit in route params",
"mnemonicPhrasePlaceholder": "Mnemonic phrase...",
"newMint": "New mint",
"nextInterval": "Next interval",
"noEcashRecovered": "No ecash recovered",
"noSpentEcashFound": "No spent ecash found in your wallet",
"nostr": {
"copyPubKey": "Copy Nostr public key",
"enterAddress": "Enter your Nostr address",
"enterAddressDesc": "Minibits uses Nostr address (nip05) as a sharable contact to send and receive ecash.",
"invalidAddressFormat": "Invalid NOSTR address, please check that it follows name@domain.com format",
"lastStandDialog": {
"cancel": "Cancel",
"complete": "Profile change is complete.",
"confirm": "Apply Change",
"desc": "Using your own Nostr address will disable Lightning address features unique to minibits.cash address.",
"readyDesc": "Wallet needs to restart to apply this change.",
"readyTitle": "Profile change is ready",
"title": "Consider before change"
},
"minibitsNameKeyReuseError": "%{domain} names and keys can't be used with multiple wallets.",
"missingRelaysError": "Missing NOSTR relays",
"pasteError": "Copy your NOSTR address first, then paste.",
"profileRelayMismatchedIdentifierError": "Profile from the relay does not match the given nip05 identifier",
"relaysTitle": "Nostr relays",
"removePub": "Remove your public key",
"removePubSubText": "Remove your npub key and stop loading public contacts.",
"setPublicKey": "Set your public key",
"setPublicKeySubText": "Add or change your NOSTR social network public key (npub).",
"setRelay": "Set relay",
"setRelaySubText": "Add or change your own relay if your profile and follows are not hosted on the default relays.",
"tip": "Tip the people you follow",
"tipSubText": "Add your NOSTR social network public key (npub) and tip or donate to your favourite people and projects directly from the minibits wallet.",
"useOwnProfile": {
"desc": "Import your own Nostr address and keys. Your wallet will stop communicating with minibits.cash Nostr and LNURL address servers, disabling Lightning address features for receiving zaps and payments. Only for hardcore ecash-ers!",
"title": "Use own Nostr profile"
}
},
"nsecEnterPrivateKey": "Enter your private key",
"nsecEnterPrivateKeyDesc": "Minibits needs your private key in nsec format in order to decrypt messages containing incoming payments. Your key will be stored in your device secure key vault.",
"nsecMissingProfileError": "Missing profile to update",
"nsecPasteError": "Copy your nsec key first, then paste.",
"nsecPrivatePublicKeyMismatchError": "Provided private key does not match your new profile public key.",
"numberOfBackedupProofs": "Number of backed up proofs",
"onDeviceInfo": "On device information",
"pasteEcashTokenDesc": "Paste ecash token you want to receive.",
"payCommon": {
"amountToPayLabel": "Amount to pay",
"amountZeroOrNegative": "Amount should be positive number.",
"completed": "Payment completed",
"failed": "Payment failed",
"insufficientFunds": "There are not enough funds to send this amount.",
"isPending": "Payment is pending",
"maximumPay": "Maximum amount to pay is %{amount} %{currency}.",
"minimumWithdraw": "Minimal withdraw amount is %{amount} %{currency}.",
"pay": "Pay",
"payFrom": "Pay from",
"payFromWallet": "Pay from wallet",
"payMe": "Pay me",
"paymentRequests": "Payment requests",
"payNow": "Pay now",
"payToAddress": "Pay to this address",
"payWithLightning": "Pay with Lightning",
"receive": "Receive",
"receiveEcash": "Receive ecash",
"receiveInPerson": "Receive in person",
"receiveOffline": "Receive offline",
"requestPayment": "Request payment",
"send": "Send",
"withdraw": "Withdraw"
},
"payerMemo": "Memo for the payer",
"paymentRequestScreen": {
"incoming": {
"noRequests": "There are no incoming payment requests to be paid or they have already expired."
},
"listItem": {
"expired": "Expired",
"expires": "Expires",
"requestPaymentSuccess": "Request has been paid"
},
"outgoing": {
"noRequests": "There are no outgoing payment requests to be paid or they have already expired.",
"scanAndPay": "Scan and pay to top-up"
}
},
"payWithLightningDesc": "Enter or paste Lightning address, invoice or LNURL pay code you want to pay to.",
"pictureRetrieveFail": "Could not retrieve pictures from the server, try again later.",
"privacyScreen": {
"logger": "Enable logging",
"loggerDescription": "Log anonymous error information in order to identify and fix wallet bugs during beta testing. Switch off for best privacy.",
"torDaemon": "Use Tor network",
"torDaemonDescription": "Allow connections with mints through Tor .onion addresses.",
"torStatus": "Tor status"
},
"privateKey": "Private key",
"privateNotePlaceholder": "Your private note",
"profileMissingAddressError": "Missing address",
"profileMissingNostrAddress": "This profile does not have nostr address to send ecash to.",
"profileOnboarding": {
"desc": "Your minibits.cash wallet address allows you to receive encrypted ecash over Nostr. Simultaneously, it serves as your Lightning address, enabling you to receive payments from any Lightning wallet or zaps on the Nostr social network.",
"minibitsDesc": "Share your wallet address to receive encrypted ecash over Nostr. This also serves as your Lightning address, enabling you to receive payments from any Lightning wallet or zaps on the Nostr social network.",
"minibitsTitle": "Your Minibits wallet address",
"ownAddrDesc": "You are using your own Nostr address to send and receive ecash. Please note that this setup does not allow you to receive Nostr zaps or Lightning payments to this address.",
"ownAddrTitle": "Your own wallet address",
"title": "Create wallet address"
},
"profilePublicKeyMismatchError": "Profile from relays public key differs from yor wallet pubkey. Reset this profile in Privacy settings and import again with new keys.",
"profileScreen": {
"changeAvatar": "Change picture",
"changeAvatarSubtext": "Select one from randomly generated pictures.",
"changeWalletaddress": "Change wallet address",
"changeWalletaddressSubtext": "Change unique wallet address. Select one of randomly generated or choose your own."
},
"profileToRecoverDesc": "This is the wallet address linked to your seed. If you press Complete, your wallet profile will reset to above seed and address.",
"pushNotifications": "Push notifications",
"qr": {
"fail": "Could not display QR code, copy or share the Ecash token instead."
},
"receiverMemo": "Memo for the receiver",
"receiveScreen": {
"memoFromSender": "Memo from sender",
"newMintsAdded": {
"one": "{{count}} mint has been added to your wallet",
"other": "{{count}} mints have been added to your wallet"
},
"pasteoReceiveDescription": "Paste ecash token to receive it into your wallet.",
"pasteToReceive": "Paste",
"received": "Amount received",
"scanToReceive": "Scan or paste to receive",
"scanToReceiveDescription": "Scan or paste ecash token or LNURL withdraw link to receive into your wallet.",
"scanWithdrawalLink": "Scan withdrawal link",
"sharePaymentRequest": "Send payment request",
"sharePaymentRequestDescription": "Send payment request to one of your contacts, so that the payer can pay you exact amount.",
"showOrShareInvoice": "Share lightning invoice",
"showOrShareInvoiceDescription": "Present or share lightning invoice for an amount you want to receive.",
"status": "Status",
"title": "Receive",
"toReceive": "Amount to receive"
},
"recoverToWallet": "Recover to wallet",
"recovery": {
"completeCTA": "Complete recovery",
"completed": "Recovery completed",
"completing": "Completing recovery...",
"failed": "Recovery failed",
"findAddressCTA": "Find wallet address",
"foundAddrParam": "Found %{addr}",
"foundPendingProofsAmount": "Found %{amount} pending proofs...",
"foundProofsAmount": "Found %{amount} proofs...",
"intervalParam": "Recovery interval %{startIndex} - %{endIndex} · ",
"keysetID": "Keyset ID %{id} (%{unit})",
"missingMnemonicSeedProfileError": "Missing mnemonic or seed or profile to recover.",
"mnemonicCollision": "Mnemonic exists",
"mnemonicCollisionDesc": "Your wallet already has another mnemonic in its secure storage. Recovery process works only with freshly installed wallet to avoid loss of your funds.",
"noKeysetSelected": "Please select keyset.",
"noMintSelected": "Please select the mint.",
"noWalletForSeedError": "Could not find wallet address linked to your seed on the server.",
"ownKeysImportAgain": "You used wallet address %{addr} with your own keys, import it again.",
"recoveredResult": "%{formattedCurrency} %{code} were recovered into your wallet.",
"recoveringAddress": "Recovering wallet address...",
"restoringFromParam": "Restoring from %{hostname}...",
"resultErrors": "Recovery ended with errors.",
"resultExpired": "Nothing has been found in this recovery interval.",
"resultSpent": "Good news is that already spent Ecash has been found. Continue with next recovery interval.",
"seedFromCurrentDeviceError": "Can not use seed from this device.",
"selectAnotherKeyset": " · Select another",
"selectKeyset": "Select keyset to be used for recovery",
"selectMintFrom": "Select mint to recover from.",
"setManually": "Set manually",
"spentProofsAmount": "%{amount} proofs were already spent...",
"startIndexDesc": "Use to increase starting index of recovery interval e.g. in case of repeated recovery. You need to know at what index you completed your previous recovery or you'll miss ecash to recover.",
"starting": "Starting recovery...",
"success": "Recovery success!"
},
"recoveryFromMints": "Recovery from mints",
"recoveryFromMintsDesc": "Identify mints to recover your ecash from and add them to the list.",
"recoveryIndexesIncSuccess": "Recovery indexes increased by %{indCount}",
"recoveryInsertMnemonic": "Insert mnemonic phrase",
"recoveryInsertMnemonicDesc": "Paste or rewrite 12 word phrase to recover your ecash balance and wallet address on this device. Separate words by blank spaces.",
"recoveryInsertMnemonicDescAddrOnly": "Paste or rewrite 12 word phrase to recover your wallet address on this device. Separate words by blank spaces.",
"recoveryInvalidMnemonicError": "Invalid mnemonic phrase. Provide 12 word sequence separated by blank spaces.",
"recoveryTool": "Recovery tool",
"relayExists": "Relay already exists",
"relays": "Relays",
"relayurlPasteError": "Copy your relay URL key before pasting",
"removeRelay": "Remove relay",
"removingMintLostBalanceWarning": "Your wallet has non-zero balance with this mint. If removed, your ecash will be lost!",
"resetAborted": "Reset aborted, there are no transactions in local database.",
"resetCompletedDetail": "Reset completed. %{transCount} recent transactions were loaded from database.",
"resetOwnProfile": "Reset own profile",
"resetOwnProfileDesc": "Stop using your own NOSTR address and re-create Minibits wallet profile.",
"scanReceiveExtractFail": "Could not extract ecash token nor LNURL withdraw link to receive.",
"scanScreen": {
"onPasteEmptyClipboard": "First copy ecash token, invoice, LNURL link or lightning address. Then paste."
},
"securityScreen": {
"biometry": "Biometric authentication",
"biometryAvailable": "Biometric authentication is now required for Minibits to start and unlock the encryption key.",
"biometryNone": "You have not setup biometric authentication or your device does not support it.",
"encryptStorage": "Encrypt storage",
"encryptStorageDescription": "Encrypt the storage that stores your ecash with the secret key generated on your device. Experimental, not recommended for every day use."
},
"sendScreen": {
"pasteToSend": "Paste",
"pasteToSendDescription": "Paste lightning invoice from clipboard to pay it from your wallet.",
"scanToPay": "Scan to pay",
"scanToPayDescription": "Scan Lightning invoice, LNURL pay code or Lightning address to pay.",
"sendToContact": "Send to contact",
"sendToContactDescription": "Send your ecash to one of your contacts stored in your contact list.",
"showOrShareToken": "Share ecash",
"showOrShareTokenDescription": "Present or share ecash token for an amount you want to send.",
"title": "Send"
},
"saveContactPrivate": "Save as private contact",
"saveContactPrivateDesc": "Save %{nip05} to your Private contacts so you can pay faster",
"setStartIndex": "Set start index",
"settingsScreen": {
"backupRecovery": "Backup and recovery",
"devOptions": "Developer options",
"go": "Let's go!",
"mintsCount": {
"one": "{{count}} mint",
"other": "{{count}} mints"
},
"preferredUnit": "Preferred unit",
"privacy": "Privacy",
"security": "Security",
"title": "Settings",
"update": "Update manager"
},
"share": {
"cancelled": "Sharing cancelled",
"contactAddress": "Share contact address"
},
"shareWalletAddress": "Share wallet address",
"showErrors": "Show errors",
"showOnboarding": "Show onboarding again",
"showOnboardingDesc": "Go through onboarding screens again next time you restart Minibits.",
"startRecovery": "Start recovery",
"syncCompleted": "Sync completed.",
"syncOwnProfile": "Sync own profile",
"syncOwnProfileDesc": "Synchronize your profile name and picture with up to date information from Nostr relays.",
"tabNavigator": {
"contactsLabel": "Contacts",
"settingsLabel": "Settings",
"walletLabel": "Wallet"
},
"tokenReceiveScreen": {
"onPasteEmptyClipboard": "First copy ecash token, then paste.",
"topupWithLightning": "Topup with Lightning"
},
"topup": {
"failed": "Topup failed",
"mint": "Topup mint",
"missingMintAddFirst": "Please add the mint first",
"missingMintAddFirstDesc": "There is no mint connected to your wallet eligible for receiving ecash.",
"nostrDMreceived": "nostr:%{npub} sent you Lightning invoice for %{amount} %{currency} from Minibits wallet!",
"relayMissingSentEvent": "Relay could not confirm that the message has been published.",
"sendToNip": "Send to %{sendTonip05}",
"to": "Topup to",
"withLightning": "Topup with Lightning",
"sendToContact": "Send to contact",
"withdrawalAvailable": "Withdrawal is available",
"withdrawAvailableDesc": "Up to %{amount} %{code} are available to withdraw",
"invoiceCreatedParam": "Invoice for %{amount} %{code} created",
"withdraw": "Withdraw",
"sendRequest": "Send request",
"invoiceCreatedDescParam": "Your selected mint balance to top up is %{mintUrl}"
},
"tranDetailScreen": {
"amount": "Amount",
"balanceAfter": "Balance after this transaction",
"claim": "Claim",
"createdAt": "Created at",
"id": "ID",
"invoice": "Lightning invoice to pay",
"isOffline": "Redeem online",
"lightningFee": "Lightning network fee",
"memoFromInvoice": "Memo from invoice",
"memoFromSender": "Memo from sender",
"receiveOfflineComplete": "Redeem to wallet",
"revert": "Revert",
"sentFrom": "Sent from",
"sentTo": "Sent to",
"status": "Status",
"topupTo": "Topup to",
"trasferredTo": "Paid to",
"type": "Type",
"addYourNote": "Add your note",
"addTags": "Add tags",
"backedUpEcash": "Backed up ecash",
"successRetrieveEcashAfterRetry": "Ecash has been successfully received after retry. New transaction has been created.",
"retryToReceive": "Retry to receive",
"auditTrail": "Audit trail",
"modalSuccess": "Success!",
"pendingToken": "Pending token",
"retryToComplete": "Retry to complete",
"invoiceToPay": "Invoice to pay",
"invoiceNotPaid": "Invoice not paid",
"invoiceExpired": "Invoice expired",
"topupError": "Topup error"
},
"transactionCommon": {
"feePaid": "Paid fee",
"from": "From %{sender}",
"paidFrom": "Paid from",
"paidTo": "Paid to %{receiver}",
"receivedFrom": "Received from %{sender}",
"receivedTo": "Received to",
"receiveFailed": "Receive failed",
"redeemOnline": "Redeem online",
"reverted": "Transaction reverted",
"sentTo": "Sent to %{receiver}",
"status": {
"blocked": "Blocked",
"completedFee": "Completed %{fee}",
"draft": "Draft",
"error": "Error",
"expired": "Expired",
"pending": "Pending",
"prepared": "Prepared",
"reverted": "Reverted",
"spent": "Spent",
"unspent": "Unspent"
},
"tapToRedeem": "Tap to redeem",
"unknown": "Unknown transaction",
"youPaid": "You paid",
"youReceived": "You received",
"youSent": "You sent"
},
"transferScreen": {
"insufficientFunds": "There is not enough balance in %{currency} to pay the invoice amount and expected fees: %{amount} %{currency}",
"LUD18unsupported": "Minibits does not yet support entering of payer identity data (LUD18).",
"noMintWithBalance": "There is no mint with %{unit} balance from which payment can be made.",
"selectMintFrom": "Select mint balance to transfer from"
},
"updateScreen": {
"gotoPlayStore": "Visit Play Store",
"nativeUpdateAvailableDesc": "There is a new version of Minibits Wallet in the Google Play Store.",
"updateAvailable": "Update is available",
"updateAvailableDesc": "There is a new Over-the-Air update of Minibits Wallet ready to download and install.",
"updateNew": "What's new?",
"updateNotAvailable": "Nothing to update",
"updateNotAvailableDesc": "Good, you are running the latest version of the app.",
"updateNow": "Update and restart",
"updateSize": "Download size",
"downloading": "Downloading update...",
"installing": "Installing update...",
"updateManagerTitle": "Update manager",
"defaultUpdateDesc": "UX and performance improvements",
"apkOnGithub": "APK on Github"
},
"userErrorMissingLightningData": "Missing Lightning invoice, paycode or address.",
"unspentOnlyRecoverable": "Only unspent proofs are recoverable.",
"walletAddressRecovery": "Wallet address recovery",
"walletAddressRecoveryDesc": "Use your mnemonic phrase from another device to get your original wallet address on this device. This will not recover balances.",
"walletScreen": {
"feeBadge": {
"final": "+ final fee %{fee} %{code}",
"upto": "+ fee up to %{fee} %{code}"
},
"mintExists": "This mint already exists",
"paymentSentSuccess": "Payment request has been successfully sent.",
"sendEcash": "Send Ecash",
"sendEcashDesc": "Share ecash or send it to one of your contacts",
"payWithLightning": "Pay with Lightning",
"payWithLightningDesc": "Pay invoice or to a Lightning address",
"receiveEcash": "Receive Ecash",
"topupWithLightning": "Topup with Lightning",
"topupWithLightningDesc": "Create Lightning invoice to topup your balance",
"receiveEcashDesc": "Paste or scan ecash token",
"addFirstMint": "Add your first mint",
"topup": "Topup",
"pay": "Pay"
},
"warning": "Warning",
"welcomeScreen": {
"page1": {
"bullet1": "Minibits follows the Cashu protocol, where mints back ecash with Bitcoin.",
"bullet2": "Ecash is issued or exchanged back to Bitcoin instantly through Lightning payments.",
"bullet3": "Ecash tokens are stored on-device; mints do not keep ledger or wallet balances.",
"final": "No Watson, there is no blockchain.",
"heading": "Welcome",
"intro": "Minibits is an ecash and Lightning wallet with a focus on performance and usability. Ecash is a bearer token issued by custodians known as mints."
},
"page2": {
"bullet1": "Minibits provides sharable wallet identifiers using NOSTR addresses. Like account numbers, just better.",
"bullet2": "Try in-person sends while devices are offline - ecash settles when back online (Proof of concept).",
"bullet3": "You can opt-in for storage encryption and biometric authentication.",
"final": "Minibits is free and open-source software; find us on Github for the roadmap and contributions.",
"heading": "Why Minibits?",
"intro": "Minibits' aim is to research how Lightning and ecash can be integrated into a seamless and instant experience that can work at scale and still provide good privacy."
},
"page3": {
"bullet1": "Mints are, by design, custodial services. Run your own or use them only for research and testing purposes.",
"bullet2": "Ecash is stored on your device, so the loss of the device means the loss of ecash. Remote backup protocol is still under research.",
"bullet3": "Minibits provides its own mint that you can use for testing purposes with small amounts. It is operated on a best-effort basis and without any guarantees.",
"final": "Now, let's move some ecash!",
"go": "Let's go!",
"heading": "Do not forget",
"intro": "Both the Cashu protocol and the Minibits wallet are still experimental, and by using them, you accept known and unknown risks."
},
"lastPageConfirmButton": "Got it, take me to the wallet",
"lastPageRecoverLostWalletButton": "Recover lost wallet",
"generatingWalletSeedStatusMessage": "Creating wallet seed, this takes a while..."
},
"contactHasNoLightningAddrUseEcash": "This contact does not have a Lightning address, send ecash instead.",
"tranHistory": {
"showingPaginationPending": "Showing %{amount} of %{total} pending",
"showingPaginationTotal": "Showing %{amount} of %{total} total",
"pendingParam": "%{param} Pending transactions",
"listIsComplete": "List is complete",
"viewMore": "View more",
"noTransToShow": "No transactions to show.",
"deleteExpired": "Delete expired",
"deleteExpiredDesc": "This will delete %{count} expired transactions",
"deleteErrored": "Delete transactions with errors",
"deleteErroredDesc": "This will delete %{count} transactions with errors"
},
"updateModal": {
"title": "New Minibits version is available",
"desc": "Updates provide new functionalities and important bug fixes. View details in the Update manager."
},
"wallerScreen": {
"mintButton": "Mint"
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"common": {
"close": "Zavrieť",
"copyFailParam": "Nepodarilo sa skopírovať %{param}",
"notCreated": "Ešte nevytvorené",
"offlinePretty": "Vaše zariadenie je momentálne offline.",
"qr": "QR kód",
"resetDefault": "Obnoviť pôvodné nastavenia",
"sats": "SATS",
"share": "Zdieľať"
},
"transferScreen": {
"noMintWithBalance": "Neexistuje žiaden mint so zostatkom %{unit} z ktorého je možné uskutočniť platbu."
},
"contactHasNoLightningAddrUseEcash": "Tento kontakt nemá Lightning adresu, namiesto toho pošlite ecash."
}
+21 -25
View File
@@ -22,6 +22,7 @@ import EventEmitter from '../utils/eventEmitter'
import {ResultModalInfo} from './Wallet/ResultModalInfo'
import {Database, log, WalletTask, WalletTaskResult} from '../services'
import { getSnapshot } from 'mobx-state-tree'
import { translate } from '../i18n'
export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(function BackupScreen(_props) {
const {navigation} = _props
@@ -50,9 +51,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
const handleSpentByMintTaskResult = async (result: WalletTaskResult) => {
log.warn('handleSpentByMintTaskResult event handler triggered')
if(!isHandleSpentFromSpendableSentToQueue) {
return false
}
if (!isHandleSpentFromSpendableSentToQueue) { return false }
setIsLoading(false)
// runs per each mint
@@ -60,12 +59,11 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
setTotalSpentAmount(prev => prev + result.spentAmount)
setTotalSpentCount(prev => prev + result.spentCount)
setInfo(
`${totalSpentCount} ecash proofs, ${totalSpentAmount} in total were removed from the wallet.`,
translate("backupScreen.result", { proofCount: totalSpentCount, amount: totalSpentAmount })
)
return
}
setInfo('No spent ecash found in your wallet')
setInfo(translate("noSpentEcashFound"))
}
EventEmitter.on('ev__handleSpentByMintTask_result', handleSpentByMintTaskResult)
@@ -93,9 +91,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
Database.addOrUpdateProofs(proofsStore.allPendingProofs, true)
}
setBackupResultMessage(
'Your minibits tokens were backed up to local database. New tokens will be backed up automatically.',
)
setBackupResultMessage(translate("backupScreen.success"))
toggleBackupModal()
setIsLoading(false)
return
@@ -103,9 +99,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
Database.removeAllProofs()
setIsLoading(false)
setBackupResultMessage(
'Your backup of minibits tokens has been deleted. New tokens will NOT be backed up.',
)
setBackupResultMessage(translate("backupScreen.deletedSuccess"))
toggleBackupModal()
} catch (e: any) {
handleError(e)
@@ -135,12 +129,13 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
const increaseCounters = async function () {
for (const mint of mintsStore.allMints) {
for(const counter of mint.proofsCounters) {
mint.increaseProofsCounter(counter.keyset, 50)
}
}
setInfo('Recovery indexes increased by 50')
const increaseAmount = 50
for (const mint of mintsStore.allMints) {
for(const counter of mint.proofsCounters) {
mint.increaseProofsCounter(counter.keyset, increaseAmount)
}
}
setInfo(translate("recoveryIndexesIncSuccess", { indCount: increaseAmount }))
}
@@ -172,8 +167,8 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
bottomSeparator={true}
/>
<ListItem
text="Wallet address recovery"
subText="Use your mnemonic phrase from another device to get your original wallet address on this device. This will not recover balances."
tx="walletAddressRecovery"
subTx="walletAddressRecoveryDesc"
leftIcon='faCircleUser'
leftIconColor={colors.palette.iconGreyBlue400}
leftIconInverse={true}
@@ -251,8 +246,8 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
HeadingComponent={
<>
<ListItem
text="Increase recovery indexes"
subText={`After incomplete recovery from seed or other rare issue, you may encounter 'duplicate outputs' error when trying to send. This resets your recovery indexes to higher value in order to resolve the issue.`}
tx="increaseRecoveryIndexes"
subTx="increaseRecoveryIndexesDesc"
leftIcon='faArrowUp'
leftIconColor={colors.palette.success300}
leftIconInverse={true}
@@ -283,9 +278,10 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
? colors.palette.success200
: colors.palette.neutral400
}
title={
isLocalBackupOn ? 'Local backup is on' : 'Local backup is off'
}
// title={
// isLocalBackupOn ? 'Local backup is on' : 'Local backup is off'
// }
title={translate(isLocalBackupOn ? 'localBackupEnabled' : 'localBackupDisabled')}
message={backupResultMessage as string}
/>
}
+20 -19
View File
@@ -15,6 +15,7 @@ import { moderateVerticalScale, verticalScale } from '@gocodingnow/rn-size-matte
import { ReceiveOption } from './ReceiveOptionsScreen'
import { SendOption } from './SendOptionsScreen'
import { IncomingDataType, IncomingParser } from '../services/incomingParser'
import { translate } from '../i18n'
interface ContactDetailScreenProps extends ContactsStackScreenProps<'ContactDetail'> {}
@@ -94,10 +95,10 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
const onNoteSave = function () {
try {
contactsStore.saveNote(contact.pubkey, note || '')
setIsNoteEditing(false)
contactsStore.saveNote(contact.pubkey, note || '')
setIsNoteEditing(false)
} catch (e: any) {
setInfo(`Could not save note: ${e.message}`)
setInfo(translate('common.saveFailParam', { param: e.message }))
}
}
@@ -115,9 +116,9 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
const onCopyNpub = function () {
try {
Clipboard.setString(contact.npub)
Clipboard.setString(contact.npub)
} catch (e: any) {
setInfo(`Could not copy: ${e.message}`)
setInfo(translate('common.copyFailParam', { param: e.message }))
}
}
@@ -143,7 +144,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
contactsStore.refreshPicture(contact.pubkey)
toggleContactModal()
setInfo('Sync completed.')
setInfo(translate("syncCompleted"))
return
} catch (e: any) {
toggleContactModal()
@@ -222,7 +223,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
maxLength={200}
keyboardType="default"
selectTextOnFocus={true}
placeholder="Your private note"
placeholder={translate("privateNotePlaceholder")}
editable={
isNoteEditing
? true
@@ -258,7 +259,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
{lud16 && (
<ListItem
text={lud16}
subText={'Lightning address'}
subTx='lightningAddress'
leftIcon='faBolt'
leftIconColor={colors.palette.orange200}
/>
@@ -266,7 +267,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
<ListItem
LeftComponent={
<Button
text={`Request payment`}
tx="payCommon.requestPayment"
style={{marginLeft: spacing.small, alignSelf: 'center', minHeight: verticalScale(20)}}
textStyle={{fontSize: moderateVerticalScale(14), lineHeight: moderateVerticalScale(16)}}
onPress={gotoTopup}
@@ -275,7 +276,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
}
RightComponent={lud16 ? (
<Button
text={`Pay to this address`}
tx="payCommon.payToAddress"
style={{marginLeft: spacing.small, alignSelf: 'center', minHeight: verticalScale(20)}}
textStyle={{fontSize: moderateVerticalScale(14), lineHeight: moderateVerticalScale(16)}}
onPress={gotoTransfer}
@@ -307,7 +308,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
<Text
size='xs'
style={{color: addressColor, marginHorizontal: spacing.large, textAlign: 'center'}}
text='This profile does not have nostr address to send ecash to.'
tx="profileMissingNostrAddress"
/>
)}
</View>
@@ -318,14 +319,14 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
ContentComponent={
<>
<ListItem
text='Share contact address'
tx="share.contactAddress"
subText={nip05}
leftIcon='faShareNodes'
onPress={onShareContact}
bottomSeparator={true}
/>
<ListItem
text="Copy contact's public key"
tx="copyContactPublicKey"
subText={npub}
leftIcon='faCopy'
onPress={onCopyNpub}
@@ -333,16 +334,16 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
{type === ContactType.PRIVATE && (
<>
<ListItem
text='Check and sync'
subText='Checks that contact name is still linked to the same pubkey and updates picture if it was changed.'
tx="checkAndSync"
subTx="checkAndSyncDesc"
leftIcon='faRotate'
onPress={onSyncPrivateContact}
topSeparator={true}
bottomSeparator={true}
/>
<ListItem
text='Delete contact'
subText='Remove this contact from your wallet.'
tx="deleteContact"
subTx="deleteContactDesc"
leftIcon='faXmark'
onPress={onDeleteContact}
/>
@@ -350,8 +351,8 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
)}
{type === ContactType.PUBLIC && (
<ListItem
text='Save as private contact'
subText={`Save ${contact.nip05} to your Private contacts so you can pay faster`}
tx="saveContactPrivate"
subText={translate('saveContactPrivateDesc', { nip05: contact.nip05 })}
leftIcon='faClipboard'
onPress={saveToPrivateContacts}
topSeparator={true}
+16 -16
View File
@@ -98,23 +98,23 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
}, [donationInvoice])
useEffect(() => {
const handleIsInvoicePaid = async () => {
if(!isInvoicePaid || !donationInvoice) {
return
}
stopPolling(`checkDonationPaidPoller-${donationInvoice.payment_hash}`)
setResultModalInfo({
status: TransactionStatus.COMPLETED,
message: translate("contactsScreen.ownName.donationSuccess", { receiver: ownName+MINIBITS_NIP05_DOMAIN })
})
toggleResultModal()
//resetState()
useEffect(() => {
const handleIsInvoicePaid = async () => {
if (!isInvoicePaid || !donationInvoice) {
return
}
handleIsInvoicePaid()
return () => {
}
stopPolling(`checkDonationPaidPoller-${donationInvoice.payment_hash}`)
setResultModalInfo({
status: TransactionStatus.COMPLETED,
message: translate("contactsScreen.ownName.donationSuccess", { receiver: ownName + MINIBITS_NIP05_DOMAIN })
})
toggleResultModal()
//resetState()
}
handleIsInvoicePaid()
return () => {
}
}, [isInvoicePaid])
const toggleResultModal = () =>
+4 -2
View File
@@ -118,7 +118,9 @@ export const PrivateContacts = observer(function (props: {
const profileRecord = await MinibitsClient.getWalletProfileByNip05(newContactName + MINIBITS_NIP05_DOMAIN)
if(!profileRecord) {
warningMessage(translate("contactsScreen.privateContacts.profileNotFound", { name: newContactName + MINIBITS_NIP05_DOMAIN }))
warningMessage(translate("contactsScreen.privateContacts.profileNotFound", {
name: newContactName + MINIBITS_NIP05_DOMAIN
}))
setIsLoading(false)
return
}
@@ -204,7 +206,7 @@ export const PrivateContacts = observer(function (props: {
if(paymentOption && paymentOption === SendOption.LNURL_ADDRESS) {
if(!contact.lud16) {
setInfo(translate("contactsScreen.privateContacts.noLightningAddress"))
setInfo(translate("contactHasNoLightningAddrUseEcash"))
return
}
+11 -11
View File
@@ -265,7 +265,7 @@ export const PublicContacts = observer(function (props: {
const onPastePublicRelay = async function () {
const url = await Clipboard.getString()
if (!url) {
setInfo(translate("contactsScreen.publicContacts.relayurlPasteError"))
setInfo(translate("relayurlPasteError"))
return
}
setNewPublicRelay(url)
@@ -276,7 +276,7 @@ export const PublicContacts = observer(function (props: {
try {
if(newPublicRelay) {
if(relaysStore.alreadyExists(newPublicRelay)) {
setInfo(translate("contactsScreen.publicContacts.relayExists"))
setInfo(translate("relayExists"))
return
}
@@ -381,7 +381,7 @@ export const PublicContacts = observer(function (props: {
if(paymentOption && paymentOption === SendOption.LNURL_ADDRESS) {
if(!contact.lud16) {
setInfo(translate('contactsScreen.publicContacts.missingLightningAddress'))
setInfo(translate('contactHasNoLightningAddrUseEcash'))
//reset
navigation.setParams({
paymentOption: undefined,
@@ -440,8 +440,8 @@ export const PublicContacts = observer(function (props: {
leftIcon='faComment'
leftIconInverse={true}
leftIconColor={colors.palette.iconViolet200}
tx="contactsScreen.publicContacts.nostrTip"
subTx='contactsScreen.publicContacts.nostrTipSubText'
tx="nostr.tip"
subTx='nostr.tipSubText'
onPress={toggleNpubModal}
/>
}
@@ -523,22 +523,22 @@ export const PublicContacts = observer(function (props: {
<>
<ListItem
leftIcon='faKey'
tx="contactsScreen.publicContacts.nostrSetPublicKey"
subTx='contactsScreen.publicContacts.nostrSetPublicKeySubText'
tx="nostr.setPublicKey"
subTx='nostr.setPublicKeySubText'
onPress={toggleNpubModal}
bottomSeparator={true}
/>
<ListItem
leftIcon='faCircleNodes'
tx='contactsScreen.publicContacts.nostrSetRelay'
subTx='contactsScreen.publicContacts.nostrSetRelaySubText'
tx='nostr.setRelay'
subTx='nostr.setRelaySubText'
onPress={toggleRelayModal}
bottomSeparator={true}
/>
<ListItem
leftIcon='faBan'
tx="contactsScreen.publicContacts.nostrRemovePub"
subTx="contactsScreen.publicContacts.nostrRemovePubSubText"
tx="nostr.removePub"
subTx="nostr.removePubSubText"
onPress={onRemovePublicPubKey}
/>
</>
+30 -35
View File
@@ -78,13 +78,11 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
transactionsStore.addTransactionsToModel(result._array)
setIsLoading(false)
setInfo(
`Reset completed. ${result.length} recent transactions were loaded from database.`,
)
setInfo(translate('resetCompletedDetail', { transCount: result.length }))
return true
}
setInfo('Reset aborted, there are no transactions in local database.')
setInfo(translate("resetAborted"))
setIsLoading(false)
return false
} catch (e: any) {
@@ -104,45 +102,42 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
} catch (e: any) {
handleError(e)
}
}
const factoryReset = async function () {
Alert.alert(
'Confirmation',
'All application data will be deleted. Are you sure you want to perform a factory reset?',
translate("common.confirmAlertTitle"),
translate("factoryResetUserConfirmDesc"),
[
{
text: 'Cancel',
text: translate('common.cancel'),
style: 'cancel',
onPress: () => {
// Action canceled
},
onPress: () => { /* Action canceled */ },
},
{
text: 'Confirm',
text: translate('common.confirm'),
onPress: async () => {
setIsLoading(true)
try {
// Delete database
Database.cleanAll()
// Delete Nostr keys
await KeyChain.removeNostrKeypair()
// Delete Encryption key
await KeyChain.removeMmkvEncryptionKey()
// Delete mnemonic
await KeyChain.removeMnemonic()
// Delete seed
await KeyChain.removeSeed()
// Clean mobx storage
MMKVStorage.clearAll()
// recreate db schema
Database.getInstance()
setIsLoading(false)
setInfo('Factory reset completed, please restart immediately.')
// Delete database
Database.cleanAll()
// Delete Nostr keys
await KeyChain.removeNostrKeypair()
// Delete Encryption key
await KeyChain.removeMmkvEncryptionKey()
// Delete mnemonic
await KeyChain.removeMnemonic()
// Delete seed
await KeyChain.removeSeed()
// Clean mobx storage
MMKVStorage.clearAll()
// recreate db schema
Database.getInstance()
setIsLoading(false)
setInfo(translate("factoryResetSuccess"))
} catch (e: any) {
handleError(e)
handleError(e)
}
},
},
@@ -199,8 +194,8 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
onPress={syncTransactionsFromDb}
/>
<ListItem
text="Show onboarding again"
subText="Go through onboarding screens again next time you restart Minibits."
tx="showOnboarding"
subTx="showOnboardingDesc"
leftIcon='faRotate'
leftIconColor={colors.light.tint}
leftIconInverse={true}
@@ -255,7 +250,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId}
<>
<ListItem
text={LogLevel.ERROR.toUpperCase()}
subText={'Log errors and crashes only, without private data.'}
subTx="loglevelErrorDesc"
leftIcon={selectedLogLevel === LogLevel.ERROR ? 'faCheckCircle' : 'faCircle'}
leftIconColor={selectedLogLevel === LogLevel.ERROR ? iconSelectedColor as string : iconColor as string}
onPress={() => onLogLevelSelect(LogLevel.ERROR)}
@@ -264,7 +259,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId}
/>
<ListItem
text={LogLevel.INFO.toUpperCase()}
subText={'Log anonymous usage, without private data.'}
subTx="loglevelInfoDesc"
leftIcon={selectedLogLevel === LogLevel.INFO ? 'faCheckCircle' : 'faCircle'}
leftIconColor={selectedLogLevel === LogLevel.INFO ? iconSelectedColor as string : iconColor as string}
onPress={() => onLogLevelSelect(LogLevel.INFO)}
@@ -273,7 +268,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId}
/>
<ListItem
text={LogLevel.DEBUG.toUpperCase()}
subText={'Log details, set only if testing or on request by support.'}
subTx="loglevelDebugDesc"
leftIcon={selectedLogLevel === LogLevel.DEBUG ? 'faCheckCircle' : 'faCircle'}
leftIconColor={selectedLogLevel === LogLevel.DEBUG ? iconSelectedColor as string : iconColor as string}
onPress={() => onLogLevelSelect(LogLevel.DEBUG)}
@@ -283,7 +278,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId}
<View style={$buttonContainer}>
<Button
preset="secondary"
text={'Close'}
tx='common.close'
onPress={toggleLogLevelSelector}
/>
</View>
+19 -25
View File
@@ -22,6 +22,7 @@ import { Mint } from '../models/Mint'
import { MintHeader } from './Mints/MintHeader'
import { moderateVerticalScale } from '@gocodingnow/rn-size-matters'
import useIsInternetReachable from '../utils/useIsInternetReachable'
import { translate } from '../i18n'
export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = function LightningPayScreen(_props) {
@@ -46,20 +47,16 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
const setUnitAndMint = () => {
try {
const {unit, mintUrl} = route.params
if(!unit) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint unit in route params')
}
if (!unit) throw new AppError(Err.VALIDATION_ERROR, 'Missing mint unit in route params');
setUnit(unit)
if(mintUrl) {
const mint = mintsStore.findByUrl(mintUrl)
setMint(mint)
if (mintUrl) {
const mint = mintsStore.findByUrl(mintUrl)
setMint(mint)
}
if(!isInternetReachable) {
setInfo('Your device is currently offline.')
}
if (!isInternetReachable) setInfo(translate('common.offlinePretty'))
} catch (e: any) {
handleError(e)
}
@@ -82,7 +79,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
const onPaste = async function() {
const clipboard = await Clipboard.getString()
if (clipboard.length === 0) {
infoMessage('First copy Lightning invoice, address or pay code to pay to. Then paste.')
infoMessage(translate('lightningPayScreen.onPasteEmptyClipboard'))
return
}
@@ -105,15 +102,14 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
const onConfirm = async function() {
if(!lightningData) {
setError({name: Err.VALIDATION_ERROR, message: 'Missing Lightning invoice, paycode or address.'})
return
if (!lightningData) {
setError({ name: Err.VALIDATION_ERROR, message: translate("userErrorMissingLightningData")})
return;
}
try {
const invoiceResult = IncomingParser.findAndExtract(lightningData as string, IncomingDataType.INVOICE)
return IncomingParser.navigateWithIncomingData(invoiceResult, navigation, unit, mint && mint.mintUrl)
const invoiceResult = IncomingParser.findAndExtract(lightningData as string, IncomingDataType.INVOICE)
return IncomingParser.navigateWithIncomingData(invoiceResult, navigation, unit, mint && mint.mintUrl)
} catch (e: any) {
const maybeLnurlAddress = LnurlUtils.findEncodedLnurlAddress(lightningData as string)
@@ -204,14 +200,14 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
<ListItem
leftIcon='faBolt'
leftIconColor={colors.palette.orange400}
text='Pay with Lightning'
tx="payCommon.payWithLightning"
bottomSeparator={true}
RightComponent={
<Button
preset='tertiary'
LeftAccessory={() => <Icon color={contactIcon} containerStyle={{paddingVertical: 0}} icon='faAddressBook' />}
onPress={gotoContacts}
text='Contacts'
tx="contacts"
textStyle={{fontSize: 12, color: contactIcon}}
/>
}
@@ -222,7 +218,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
<Text
size='xs'
style={{color: hintText, padding: spacing.extraSmall}}
text='Enter or paste Lightning address, invoice or LNURL pay code you want to pay to.'
tx="payWithLightningDesc"
/>
<View style={{alignItems: 'center', marginTop: spacing.small}}>
<TextInput
@@ -242,7 +238,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
<View style={$buttonContainer}>
<Button
preset='default'
tx={'common.confirm'}
tx='common.confirm'
onPress={onConfirm}
style={{marginLeft: spacing.small}}
LeftAccessory={() => <Icon icon='faCheckCircle' color='white'/>}
@@ -252,11 +248,9 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
<View style={$buttonContainer}>
<Button
preset='secondary'
tx={'common.paste'}
tx='common.paste'
onPress={onPaste}
LeftAccessory={() => (
<Icon icon='faPaste'/>
)}
LeftAccessory={() => <Icon icon='faPaste'/> }
/>
<Button
preset='secondary'
@@ -281,7 +275,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
}
/>
<Button
text={'Send as ecash'}
tx="common.sendAsEcash"
LeftAccessory={() => (
<Icon
icon='faMoneyBill1'
+41 -41
View File
@@ -38,6 +38,7 @@ import { WalletUtils } from '../services/wallet/utils'
import { MintUnit, MintUnits, getCurrency } from '../services/wallet/currency'
import { ScrollView } from 'react-native-gesture-handler'
import { CurrencyAmount } from './Wallet/CurrencyAmount'
import { translate } from '../i18n'
interface LocalRecoveryScreenProps
extends SettingsStackScreenProps<'LocalRecovery'> {}
@@ -138,8 +139,8 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
setIsLoading(true)
const encodedTokens: string[] = []
if(mintsStore.allMints.length === 0) {
setInfo('Please add all mints your proofs belong to the wallet. Then try again.')
if (mintsStore.allMints.length === 0) {
setInfo(translate("missingMintsForProofsUserMessage"))
}
for (const mint of mintsStore.allMints) {
@@ -180,14 +181,14 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
setIsLoading(false)
} catch (e: any) {
setInfo(`Could not copy: ${e.message}`)
setInfo(translate('common.copyFailParam', { param: e.message }))
}
}
const onRecovery = async function () {
if(!showUnspentOnly) {
setInfo('Can not recover other then unspent proofs.')
if (!showUnspentOnly) {
setInfo(translate("unspentOnlyRecoverable"))
return
}
@@ -199,33 +200,34 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
log.trace('[onRecovery]', nonZeroBalances)
if (nonZeroBalances && nonZeroBalances.length > 0) {
message = `Your wallet has non zero balance. If you continue, existing ecash will be deleted and replaced by backup.\n\n`
message = translate("backupWillOverwriteBalanceWarning")
message += "\n\n"
}
message += `Do you really want to recover ecash from the backup?`
message += translate("confirmBackupRecovery")
Alert.alert(
'Attention!',
message,
[
translate("attention"),
message,
[
{
text: 'Cancel',
style: 'cancel',
onPress: () => {
text: translate('common.cancel'),
style: 'cancel',
onPress: () => {
// Action canceled
},
},
},
{
text: 'Start recovery',
onPress: () => {
try {
doLocalRecovery()
} catch (e: any) {
handleError(e)
}
},
text: translate("startRecovery"),
onPress: () => {
try {
doLocalRecovery()
} catch (e: any) {
handleError(e)
}
},
},
],
],
)
}
@@ -233,12 +235,12 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
const doLocalRecovery = async function () {
try {
if(!showUnspentOnly) {
setInfo('Can not recover other then unspent proofs.')
setInfo(translate('unspentOnlyRecoverable'))
return
}
if(mintsStore.allMints.length === 0) {
setInfo('Please add all your mints to the wallet before recovery. Then try again.')
setInfo(translate('missingMintsForProofsUserMessage'))
}
setIsLoading(true)
@@ -249,16 +251,14 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
let transactionData: TransactionData[] = []
for (const proof of proofs) {
const proofMint = CashuUtils.getMintFromProof(proof, mintsStore.allMints)
const { isPending, isSpent, updatedAt, ...cleanedProof } = proof
const proofMint = CashuUtils.getMintFromProof(proof, mintsStore.allMints)
const { isPending, isSpent, updatedAt, ...cleanedProof } = proof
if (!proofMint) { continue }
if (!proofMint) { continue }
if(mint.mintUrl === proofMint.mintUrl) {
proofsByMint.push(cleanedProof)
}
if (mint.mintUrl === proofMint.mintUrl) {
proofsByMint.push(cleanedProof)
}
}
if (proofsByMint.length > 0) {
@@ -371,7 +371,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text
preset="heading"
text="Recovery tool"
tx="recoveryTool"
style={{color: 'white'}}
/>
</View>
@@ -381,7 +381,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
ContentComponent={
<>
<ListItem
text={'Unspent'}
tx="transactionCommon.status.unspent"
LeftComponent={
<Icon
containerStyle={$iconContainer}
@@ -395,7 +395,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
// bottomSeparator={true}
/>
<ListItem
text={'Pending'}
tx="transactionCommon.status.pending"
LeftComponent={
<Icon
containerStyle={$iconContainer}
@@ -409,7 +409,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
// bottomSeparator={true}
/>
<ListItem
text={'Spent'}
tx="transactionCommon.status.spent"
LeftComponent={
<Icon
containerStyle={$iconContainer}
@@ -429,7 +429,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
ContentComponent={
<>
<ListItem
text={'Number of backed up proofs'}
tx="numberOfBackedupProofs"
RightComponent={
<Text text={`${proofs.length}`} style={{marginRight: spacing.small}} />
}
@@ -458,7 +458,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
<Button
preset="tertiary"
onPress={copyBackupProofs}
text="Copy proofs"
tx="copyProofs"
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
@@ -470,7 +470,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
<Button
preset="tertiary"
onPress={copyEncodedTokens}
text="Copy as encoded tokens"
tx="copyAsEncodedTokens"
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
@@ -487,7 +487,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
<View style={$buttonContainer}>
<Button
onPress={onRecovery}
text={`Recover to wallet`}
tx="recoverToWallet"
/>
</View>
</View>
+13 -14
View File
@@ -30,9 +30,8 @@ import { CommonActions } from '@react-navigation/native'
import { StackActions } from '@react-navigation/native';
import { isObj } from '@cashu/cashu-ts/src/utils'
if (Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
}
export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer(function MintInfoScreen(_props) {
@@ -122,7 +121,7 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text
preset="heading"
text="Mint info"
tx="mintInfoHeading"
style={{color: 'white'}}
/>
</View>
@@ -147,7 +146,7 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
))
)}
{isLoading && <Loading style={{backgroundColor: 'transparent'}} statusMessage='Loading public info' />}
{isLoading && <Loading style={{backgroundColor: 'transparent'}} statusMessage={translate("loadingPublicInfo")} />}
</>
}
style={$card}
@@ -157,15 +156,15 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
ContentComponent={
<>
<ListItem
text={'On device information'}
RightComponent={<View style={$rightContainer}>
<Button
onPress={toggleLocalInfo}
text={isLocalInfoVisible ? 'Hide' : 'Show'}
preset='secondary'
/>
</View>
}
tx="onDeviceInfo"
RightComponent={<View style={$rightContainer}>
<Button
onPress={toggleLocalInfo}
text={isLocalInfoVisible ? 'Hide' : 'Show'}
preset='secondary'
/>
</View>
}
/>
{isLocalInfoVisible && (
<JSONTree
+1 -1
View File
@@ -97,7 +97,7 @@ export const MintBalanceSelector = observer(function (props: {
ListFooterComponent={props.mintBalances.length > 1 ? (
<View>
<Button
text={`${allVisible ? 'Hide' : 'Show'} more`}
tx={allVisible ? 'common.hideMore' : 'common.showMore'}
onPress={toggleAllVisible}
preset='tertiary'
/>
+25 -27
View File
@@ -117,14 +117,13 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
if (!selectedMint) {return}
try {
setIsLoading(true)
await mintsStore.updateMint(selectedMint.mintUrl)
setInfo('Mint settings have been updated')
setIsLoading(true)
await mintsStore.updateMint(selectedMint.mintUrl)
setInfo(translate("mintSettingsUpdated"))
} catch (e: any) {
handleError(e)
} finally {
setIsLoading(false)
handleError(e)
} finally {
setIsLoading(false)
}
}
@@ -143,24 +142,23 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
let message: string = ''
if (proofsByMint && proofsByMint.length > 0) {
message = `Your wallet has non-zero balance with this mint. If removed, your ecash will be lost!\n\n`
message = translate("removingMintLostBalanceWarning") + `\n\n`
}
message += `Do you really want to remove ${selectedMint.hostname} - ${selectedMint.shortname} from the wallet?`
message += translate("confirmMintRemoval", { hostname: selectedMint.hostname, shortname: selectedMint.shortname })
Alert.alert(
'Warning',
translate("warning"),
message,
[
{
text: 'Cancel',
text: translate('common.cancel'),
style: 'cancel',
onPress: () => {
// Action canceled
},
},
{
text: 'Confirm',
text: translate('common.confirm'),
onPress: () => {
try {
onMintUnselect()
@@ -234,7 +232,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
return (
<Screen preset="auto" contentContainerStyle={$screen}>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text preset="heading" text="Manage mints" style={{color: 'white'}} />
<Text preset="heading" tx="manageMints" style={{color: 'white'}} />
</View>
<View style={$contentContainer}>
<Card
@@ -242,7 +240,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
ContentComponent={
<>
<ListItem
text={'Add mint'}
tx='mintsScreen.addMint'
LeftComponent={<Icon
containerStyle={$iconContainer}
icon="faPlus"
@@ -255,7 +253,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
/>
{!mintsStore.alreadyExists(defaultMintUrl) && (
<ListItem
text={'Add Minibits mint'}
tx="mintsScreen.addMintMinibits"
LeftComponent={<SvgXml
width={spacing.medium}
height={spacing.medium}
@@ -303,14 +301,14 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
<ListItem
leftIcon="faInfoCircle"
onPress={gotoInfo}
tx={'mintsScreen.mintInfo'}
tx='mintsScreen.mintInfo'
bottomSeparator={true}
style={{paddingHorizontal: spacing.medium}}
/>
<ListItem
leftIcon='faRotate'
onPress={updateMint}
text={'Refresh mint settings'}
tx="mintsScreen.refreshMintSettings"
bottomSeparator={true}
style={{paddingHorizontal: spacing.medium}}
/>
@@ -318,7 +316,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
<ListItem
leftIcon="faShieldHalved"
onPress={unblockMint}
tx={'mintsScreen.unblockMint'}
tx='mintsScreen.unblockMint'
bottomSeparator={true}
style={{paddingHorizontal: spacing.medium}}
/>
@@ -326,7 +324,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
<ListItem
leftIcon="faShieldHalved"
onPress={blockMint}
tx={'mintsScreen.blockMint'}
tx='mintsScreen.blockMint'
bottomSeparator={true}
style={{paddingHorizontal: spacing.medium}}
/>
@@ -341,14 +339,14 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
<ListItem
leftIcon="faCopy"
onPress={onCopyMintUrl}
tx={'mintsScreen.copy'}
tx='mintsScreen.copy'
bottomSeparator={true}
style={{paddingHorizontal: spacing.medium}}
/>
<ListItem
leftIcon="faXmark"
onPress={removeMint}
tx={'mintsScreen.removeMint'}
tx='mintsScreen.removeMint'
bottomSeparator={true}
style={{paddingHorizontal: spacing.medium}}
/>
@@ -363,7 +361,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
<View style={$bottomModal}>
<Text
preset="subheading"
tx={'mintsScreen.addMintUrl'}
tx='mintsScreen.addMintUrl'
// style={{marginBottom: spacing.medium, textAlign: 'center'}}
/>
<View style={{flexDirection: 'row', alignItems: 'center', marginTop: spacing.small}}>
@@ -380,7 +378,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
/>
<Button
preset='secondary'
text="Paste"
tx='common.paste'
style={{
borderRadius: 0,
marginLeft: 1,
@@ -389,7 +387,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
/>
<Button
preset='secondary'
text="Scan"
tx="common.scan"
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
@@ -400,7 +398,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
</View>
<View style={$buttonContainer}>
<Button
text="Save"
tx='common.save'
style={{
// borderTopLeftRadius: 0,
// borderBottomLeftRadius: 0,
@@ -410,7 +408,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
onPress={addMint}
/>
<Button
tx={'common.cancel'}
tx='common.cancel'
onPress={toggleAddMintModal}
preset="secondary"
/>
+65 -50
View File
@@ -13,6 +13,7 @@ import { log } from '../services/logService'
import { KeyChain, KeyPair, NostrClient, NostrProfile } from '../services'
import { MINIBITS_NIP05_DOMAIN } from '@env'
import { ProfileHeader } from './Contacts/ProfileHeader'
import { translate } from '../i18n'
if (Platform.OS === 'android' &&
@@ -63,7 +64,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
const onPasteOwnNip05 = async function () {
const nip = await Clipboard.getString()
if (!nip) {
setInfo('Copy your NOSTR address first, then paste.')
setInfo(translate("nostr.pasteError"))
return
}
setOwnNip05(nip)
@@ -76,12 +77,12 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
const nip05Domain = NostrClient.getDomainFromNip05(ownNip05)
if(!nip05Name || !nip05Domain) {
setInfo(`Invalid NOSTR address, please check that it follows name@domain.com format`)
setInfo(translate("nostr.invalidAddressFormat"))
}
if(MINIBITS_NIP05_DOMAIN.includes(nip05Domain as string)) {
setInfo(`${MINIBITS_NIP05_DOMAIN} names and keys can't be used with multiple wallets.`)
return
setInfo(translate("nostr.minibitsNameKeyReuseError", { domain: MINIBITS_NIP05_DOMAIN }))
return
}
setIsLoading(true)
@@ -109,24 +110,34 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
const profile: NostrProfile | undefined = await NostrClient.getProfileFromRelays(nip05Pubkey, relaysToConnect)
if(!profile) {
throw new AppError(Err.VALIDATION_ERROR, 'Could not retrieve profile from relays', {nip05Pubkey, relaysToConnect})
throw new AppError(Err.VALIDATION_ERROR, "Could not retrieve profile from relays", {
nip05Pubkey, relaysToConnect
})
}
// check that the profile's nip05 matches the one given by user and living on nip05 .well-known server
if(!profile.nip05) {
if(profile.name && profile.name.toLowerCase() === nip05Name) {
profile.nip05 = ownNip05
profile.nip05 = ownNip05
} else {
throw new AppError(Err.VALIDATION_ERROR, 'Profile from the relay does not match the given nip05 identifier', {ownNip05, profile})
throw new AppError(
Err.VALIDATION_ERROR,
translate("nostr.profileRelayMismatchedIdentifierError"),
{ ownNip05, profile }
)
}
}
if(profile.nip05 !== ownNip05) {
throw new AppError(Err.VALIDATION_ERROR, 'Profile from the relay does not match the given nip05 identifier', {ownNip05, profile})
throw new AppError(
Err.VALIDATION_ERROR,
translate("nostr.profileRelayMismatchedIdentifierError"),
{ ownNip05, profile }
)
}
if(!profile.name) {
profile.name = nip05Name as string
profile.name = nip05Name as string
}
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
@@ -144,7 +155,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
const onPasteOwnNsec = async function () {
const key = await Clipboard.getString()
if (!key) {
setInfo('Copy your nsec key first, then paste.')
setInfo(translate("nsecPasteError"))
return
}
setOwnNsec(key)
@@ -154,14 +165,14 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
const onConfirmOwnNsec = async function () {
try {
if(!ownProfile) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing profile to update')
throw new AppError(Err.VALIDATION_ERROR, translate("nsecMissingProfileError"))
}
// validate that nsec matches profile pubkey
const privateKey = NostrClient.getHexkey(ownNsec)
const publicKey = getPublicKey(privateKey)
if(publicKey !== ownProfile.pubkey) {
throw new AppError(Err.VALIDATION_ERROR, 'Provided private key does not match your new profile public key.', {publicKey})
throw new AppError(Err.VALIDATION_ERROR, translate("nsecPrivatePublicKeyMismatchError"), {publicKey})
}
setOwnKeyPair({publicKey, privateKey})
@@ -175,7 +186,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
const onConfirmChange = async function () {
try {
if(!ownProfile || !ownKeyPair) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing profile to update')
throw new AppError(Err.VALIDATION_ERROR, translate('nsecMissingProfileError'))
}
setIsLoading(true)
@@ -227,36 +238,40 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
{!ownProfile ? (
<View style={$nip05Container}>
<ListItem
LeftComponent={<View style={[$numIcon, {backgroundColor: iconNip05}]}><Text text='1'/></View>}
text='Enter your Nostr address'
subText={'Minibits uses Nostr address (nip05) as a sharable contact to send and receive ecash.'}
bottomSeparator={true}
style={{}}
LeftComponent={<View style={[$numIcon, {backgroundColor: iconNip05}]}><Text text='1'/></View>}
tx="nostr.enterAddress"
subTx="nostr.enterAddressDesc"
bottomSeparator={true}
style={{}}
/>
<View style={{flexDirection: 'row', alignItems: 'center', marginVertical: spacing.medium}}>
<View style={{
flexDirection: 'row',
alignItems: 'center',
marginVertical: spacing.medium
}}>
<TextInput
ref={ownNip05InputRef}
onChangeText={(name) => setOwnNip05(name.trim())}
value={ownNip05}
autoCapitalize='none'
keyboardType='default'
maxLength={30}
placeholder='name@domain.com'
selectTextOnFocus={true}
style={[$nip05Input, {backgroundColor: inputBg}]}
ref={ownNip05InputRef}
onChangeText={(name) => setOwnNip05(name.trim())}
value={ownNip05}
autoCapitalize='none'
keyboardType='default'
maxLength={30}
placeholder='name@domain.com'
selectTextOnFocus={true}
style={[$nip05Input, {backgroundColor: inputBg}]}
/>
<Button
tx={'common.paste'}
preset='secondary'
style={$pasteButton}
onPress={onPasteOwnNip05}
tx='common.paste'
preset='secondary'
style={$pasteButton}
onPress={onPasteOwnNip05}
/>
<Button
tx={'common.confirm'}
style={$saveButton}
onPress={onConfirmOwnNip05}
tx='common.confirm'
style={$saveButton}
onPress={onConfirmOwnNip05}
/>
</View>
</View>
</View>
) : (
<>
@@ -278,7 +293,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
{ownProfileRelays.length > 0 && (
<ListItem
leftIcon='faCircleNodes'
text='Relays'
tx='relays'
subText={ownProfileRelays.toString()}
topSeparator={true}
style={{}}
@@ -287,7 +302,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
{isSetupCompleted && (
<ListItem
leftIcon='faKey'
text='Private key'
tx='privateKey'
subText={ownNsec}
topSeparator={true}
style={{}}
@@ -305,8 +320,8 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
<View style={$nip05Container}>
<ListItem
LeftComponent={<View style={[$numIcon, {backgroundColor: iconNip05}]}><Text text='2'/></View>}
text='Enter your private key'
subText={'Minibits needs your private key in nsec format in order to decrypt messages containing incoming payments. Your key will be stored in your device secure key vault.'}
tx="nsecEnterPrivateKey"
subTx="nsecEnterPrivateKeyDesc"
bottomSeparator={true}
style={{}}
/>
@@ -324,13 +339,13 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
style={[$nip05Input, {backgroundColor: inputBg}]}
/>
<Button
tx={'common.paste'}
tx='common.paste'
preset='secondary'
style={$pasteButton}
onPress={onPasteOwnNsec}
/>
<Button
tx={'common.confirm'}
tx='common.confirm'
style={$saveButton}
onPress={onConfirmOwnNsec}
/>
@@ -348,14 +363,14 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
<>
<ListItem
leftIcon='faTriangleExclamation'
text='Consider before change'
subText={'Using your own Nostr address will disable Lightning address features unique to minibits.cash address.'}
tx="nostr.lastStandDialog.title"
subTx="nostr.lastStandDialog.desc"
/>
<ListItem
leftIcon='faCheckCircle'
leftIconColor={colors.palette.success200}
text='Profile change is ready'
subText='Wallet needs to restart to apply this change.'
tx="nostr.lastStandDialog.readyTitle"
subText="nostr.lastStandDialog.readyDesc"
style={{}}
/>
</>
@@ -364,12 +379,12 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
<View style={$buttonContainer}>
<Button
preset="default"
text={'Apply change'}
tx='nostr.lastStandDialog.confirm'
onPress={onConfirmChange}
/>
<Button
preset="secondary"
text={'Save my soul'}
tx='nostr.lastStandDialog.cancel'
onPress={onCancelChange}
/>
</View>
@@ -385,12 +400,12 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
<ProfileHeader headerBg='transparent' />
<Text
style={{color: textResult, textAlign: 'center', marginTop: spacing.small}}
text={'Profile change is complete.'}
tx="nostr.lastStandDialog.complete"
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={() => {
navigation.navigate('Contacts', {})
}}
@@ -74,11 +74,11 @@ export const OutgoingRequests = observer(function (props: {
if (result.action === Share.sharedAction) {
setTimeout(
() => infoMessage(translate("paymentRequestScreen.outgoing.invoiceShared")),
() => infoMessage(translate("lightningInvoiceSharedWaiting")),
500,
)
} else if (result.action === Share.dismissedAction) {
infoMessage(translate("paymentRequestScreen.outgoing.sharingCancelled"))
infoMessage(translate("share.cancelled"))
}
} catch (e: any) {
handleError(e)
@@ -90,7 +90,7 @@ export const OutgoingRequests = observer(function (props: {
try {
Clipboard.setString(selectedRequest?.encodedInvoice as string)
} catch (e: any) {
setInfo(translate("common.copyFailParam", { param: e.message }))
setInfo(translate('common.copyFailParam', { param: e.message }))
}
}
@@ -84,7 +84,7 @@ export const PaymentRequestListItem = observer(function (
{pr.type === PaymentRequestType.INCOMING ? (
<>
<Text
tx={'paymentCommon.pay'}
tx={'payCommon.pay'}
style={{marginBottom: spacing.small}}
/>
<CurrencyAmount
@@ -97,7 +97,7 @@ export const PaymentRequestListItem = observer(function (
) : (
<>
<Text
tx={'paymentCommon.payMe'}
tx={'payCommon.payMe'}
style={{marginBottom: spacing.small}}
/>
<CurrencyAmount
@@ -160,7 +160,7 @@ export const PaymentRequestListItem = observer(function (
<Text
size="xxs"
style={{color: dim, marginBottom: -10}}
text={translate('paymentCommon.pay')}
text={translate('payCommon.pay')}
/>
</View>
<Text
@@ -259,8 +259,8 @@ export const PaymentRequestListItem = observer(function (
preset="secondary"
tx={
pr.type === PaymentRequestType.INCOMING
? "paymentCommon.payFromWallet"
: 'paymentCommon.receiveInPerson'
? "payCommon.payFromWallet"
: 'payCommon.receiveInPerson'
}
onPress={onPressPaymentRequest}
/>
+1 -1
View File
@@ -50,7 +50,7 @@ export const PaymentRequestsScreen: FC<PaymentRequestsScreenProps> = observer(fu
return (
<Screen contentContainerStyle={$screen}>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text preset="heading" text="Payment requests" style={{color: 'white'}} />
<Text preset="heading" tx="payCommon.paymentRequests" style={{color: 'white'}} />
</View>
<TabView
renderTabBar={renderTabBar}
+3 -3
View File
@@ -92,7 +92,7 @@ export const PictureScreen: FC<PictureScreenProps> = observer(function PictureSc
<Card
ContentComponent={<ListItem
leftIcon='faXmark'
text='Could not retrieve pictures from the server, try again later.'
tx="pictureRetrieveFail"
/>}
/>
)}
@@ -101,12 +101,12 @@ export const PictureScreen: FC<PictureScreenProps> = observer(function PictureSc
<View style={$buttonContainer}>
<Button
preset="default"
tx={'common.confirm'}
tx='common.confirm'
onPress={onPictureConfirm}
/>
<Button
preset="secondary"
tx={'common.cancel'}
tx='common.cancel'
onPress={() => setSelectedPicture('')}
/>
</View>
+8 -4
View File
@@ -24,6 +24,7 @@ import { log } from '../services/logService'
import { OwnKeysScreen } from './OwnKeysScreen'
import { MINIBITS_NIP05_DOMAIN } from '@env'
import { CommonActions, StackActions } from '@react-navigation/native'
import { translate } from '../i18n'
enum TorStatus {
NOTINIT = 'NOTINIT',
@@ -305,8 +306,11 @@ export const PrivacyScreen: FC<SettingsStackScreenProps<'Privacy'>> = observer(f
ContentComponent={
<>
<ListItem
text="Use own Nostr profile"
subText={walletProfileStore.isOwnProfile ? walletProfileStore.nip05 : "Import your own Nostr address and keys. Your wallet will stop communicating with minibits.cash Nostr and LNURL address servers, disabling Lightning address features for receiving zaps and payments. Only for hardcore ecash-ers!"}
tx="nostr.useOwnProfile.title"
subText={walletProfileStore.isOwnProfile
? walletProfileStore.nip05
: translate("nostr.useOwnProfile.desc")
}
leftIcon={'faShareNodes'}
leftIconColor={
walletProfileStore.isOwnProfile
@@ -320,14 +324,14 @@ export const PrivacyScreen: FC<SettingsStackScreenProps<'Privacy'>> = observer(f
<Button
style={{maxHeight: 10, marginTop: spacing.medium}}
preset="secondary"
text="Reset"
tx="common.reset"
onPress={resetProfile}
/>
) : (
<Button
style={{maxHeight: 10, marginTop: spacing.medium}}
preset="secondary"
text="Import"
tx="common.import"
onPress={gotoOwnKeys}
/>
)}
+31 -22
View File
@@ -12,6 +12,7 @@ import { log } from '../services/logService'
import { KeyChain, MinibitsClient, NostrClient, NostrProfile } from '../services'
import { MINIBITS_NIP05_DOMAIN } from '@env'
import { StackActions } from '@react-navigation/native'
import { translate } from '../i18n'
interface ProfileScreenProps extends ContactsStackScreenProps<'Profile'> {}
@@ -93,17 +94,17 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
const onCopyNpub = function () {
try {
Clipboard.setString(npub)
Clipboard.setString(npub)
} catch (e: any) {
setInfo(`Could not copy: ${e.message}`)
setInfo(translate('common.copyFailParam', { param: e.message }))
}
}
const onCopyNip05 = function () {
try {
Clipboard.setString(nip05)
Clipboard.setString(nip05)
} catch (e: any) {
setInfo(`Could not copy: ${e.message}`)
setInfo(translate('common.copyFailParam', { param: e.message }))
}
}
@@ -114,13 +115,21 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
toggleUpdateModal()
if(!walletProfileStore.nip05) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing address', {caller: 'onSyncOwnProfile'})
throw new AppError(
Err.VALIDATION_ERROR,
translate("profileMissingAddressError"),
{ caller: 'onSyncOwnProfile' }
)
}
const profile: NostrProfile = await NostrClient.getNormalizedNostrProfile(walletProfileStore.nip05, relaysStore.allUrls)
if(profile.pubkey !== walletProfileStore.pubkey) {
throw new AppError(Err.VALIDATION_ERROR, 'Profile from relays public key differs from yor wallet pubkey. Reset this profile in Privacy settings and import again with new keys.', {caller: 'onSyncOwnProfile', profile, pubkey: walletProfileStore.pubkey})
throw new AppError(
Err.VALIDATION_ERROR,
translate("profilePublicKeyMismatchError"),
{caller: 'onSyncOwnProfile', profile, pubkey: walletProfileStore.pubkey}
)
}
log.trace('[onSyncOwnProfile]', {profile})
@@ -133,7 +142,7 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
})
setIsLoading(false)
setInfo('Sync completed')
setInfo(translate('syncCompleted'))
return
} catch (e: any) {
handleError(e)
@@ -211,13 +220,13 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
{!walletProfileStore.pubkey || !walletProfileStore.picture ? (
<>
<ListItem
text='Create wallet address'
subText='Your minibits.cash wallet address allows you to receive encrypted ecash over Nostr. Simultaneously, it serves as your Lightning address, enabling you to receive payments from any Lightning wallet or zaps on the Nostr social network.'
tx="profileOnboarding.title"
subTx="profileOnboarding.desc"
/>
<View style={$buttonContainer}>
<Button
preset='secondary'
text={'Create'}
tx="common.create"
LeftAccessory={() => <Icon icon='faCircleUser'/>}
onPress={createProfileIfNotExists}
/>
@@ -227,16 +236,16 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
<>
{walletProfileStore.isOwnProfile ? (
<ListItem
text='Your own wallet address'
subText={`You are using your own Nostr address to send and receive ecash. Please note that this setup does not allow you to receive Nostr zaps or Lightning payments to this address.`}
tx="profileOnboarding.ownAddrTitle"
subTx="profileOnboarding.ownAddrDesc"
leftIcon='faCircleUser'
bottomSeparator={true}
style={{paddingRight: spacing.small}}
/>
) : (
<ListItem
text='Your Minibits wallet address'
subText={`Share your wallet address to receive encrypted ecash over Nostr. This also serves as your Lightning address, enabling you to receive payments from any Lightning wallet or zaps on the Nostr social network.`}
tx="profileOnboarding.minibitsTitle"
subTx="profileOnboarding.minibitsDesc"
leftIcon='faCircleUser'
bottomSeparator={true}
style={{paddingRight: spacing.small}}
@@ -245,13 +254,13 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
<View style={$buttonContainer}>
<Button
preset='secondary'
text={'Share'}
tx='common.share'
LeftAccessory={() => <Icon icon='faShareNodes'/>}
onPress={toggleShareModal}
/>
<Button
preset='secondary'
text={'Change'}
tx="common.change"
style={{marginLeft: spacing.small}}
LeftAccessory={() => <Icon icon='faRotate'/>}
onPress={toggleUpdateModal}
@@ -277,15 +286,15 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
{walletProfileStore.isOwnProfile && (
<>
<ListItem
text='Sync own profile'
subText='Synchronize your profile name and picture with up to date information from Nostr relays.'
tx="syncOwnProfile"
subTx="syncOwnProfileDesc"
leftIcon='faRotate'
onPress={onSyncOwnProfile}
bottomSeparator={true}
/>
<ListItem
text='Reset own profile'
subText='Stop using your own NOSTR address and re-create Minibits wallet profile.'
tx="resetOwnProfile"
subTx="resetOwnProfileDesc"
leftIcon='faXmark'
onPress={gotoPrivacy}
/>
@@ -302,14 +311,14 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
ContentComponent={
<>
<ListItem
text='Share wallet address'
tx="shareWalletAddress"
subText={nip05}
leftIcon='faShareNodes'
onPress={onShareContact}
bottomSeparator={true}
/>
<ListItem
text='Copy Nostr public key'
tx="nostr.copyPubKey"
subText={npub}
leftIcon='faCopy'
onPress={onCopyNpub}
+2 -4
View File
@@ -50,14 +50,12 @@ export const ReceiveOptionsScreen: FC<WalletStackScreenProps<'ReceiveOptions'>>
const gotoTopup = function () {
navigation.navigate('Topup', {
paymentOption: ReceiveOption.SHOW_INVOICE}
)
navigation.navigate('Topup', { paymentOption: ReceiveOption.SHOW_INVOICE })
}
const onScan = async function () {
navigation.navigate('Scan')
navigation.navigate('Scan')
}
+20 -23
View File
@@ -35,6 +35,7 @@ import { MintHeader } from './Mints/MintHeader'
import { round, toNumber } from '../utils/number'
import numbro from 'numbro'
import { TranItem } from './TranDetailScreen'
import { translate } from '../i18n'
export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
function ReceiveScreen({route, navigation}) {
@@ -91,14 +92,14 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
if (error) {
setResultModalInfo({
status,
title: error.params?.message ? error.message : 'Receive failed',
message: error.params?.message || error.message,
status,
title: error.params?.message ? error.message : translate("transactionCommon.receiveFailed"),
message: error.params?.message || error.message,
})
} else {
setResultModalInfo({
status,
message,
status,
message,
})
}
@@ -151,10 +152,8 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
log.trace('decoded token', {decoded})
log.trace('tokenAmounts', {tokenAmounts})
if(!decoded.unit) {
setInfo(`Currency unit is missing in the received token. Wallet will assume token amount is in bitcoin ${CurrencyCode.SATS}. Do not continue if you are not sure this is correct.`)
setInfo(translate("decodedMissingCurrencyUnit", { unit: CurrencyCode.SATS }))
}
const currency = getCurrency(decoded.unit)
@@ -203,8 +202,6 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
memo,
encodedToken as string,
)
}
@@ -293,7 +290,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
key={mintUrl}
text={new URL(mintUrl).hostname}
topSeparator={true}
RightComponent={<Text size='xs' text='New mint' style={$newBadge}/>}
RightComponent={<Text size='xs' tx="newMint" style={$newBadge}/>}
/>
)
} else {
@@ -318,13 +315,13 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
ContentComponent={
<>
<TranItem
label="tranDetailScreen.receivedTo"
label="transactionCommon.receivedTo"
isFirst={true}
value={mintsStore.findByUrl(transaction.mint)?.shortname as string}
/>
{transaction?.memo && (
<TranItem
label="tranDetailScreen.memoFromSender"
label="receiveScreen.memoFromSender"
value={transaction.memo as string}
/>
)}
@@ -335,7 +332,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
isCurrency={true}
/>
<TranItem
label="tranDetailScreen.status"
label="receiveScreen.status"
value={transaction.status as string}
/>
</>
@@ -357,7 +354,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
<View style={$buttonContainer}>
{isInternetReachable ? (
<Button
tx={'receiveScreen.receive'}
tx='payCommon.receive'
onPress={receiveToken}
style={{marginRight: spacing.medium}}
LeftAccessory={() => (
@@ -370,7 +367,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
/>
) : (
<Button
tx={'receiveScreen.receiveOffline'}
tx='payCommon.receiveOffline'
onPress={receiveOfflineToken}
style={{marginRight: spacing.medium}}
LeftAccessory={() => (
@@ -384,7 +381,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
)}
<Button
preset="secondary"
tx={'common.cancel'}
tx='common.cancel'
onPress={gotoWallet}
/>
</View>
@@ -405,13 +402,13 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
<ResultModalInfo
icon={'faCheckCircle'}
iconColor={colors.palette.success200}
title="Success!"
title={translate('common.success')}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={gotoWallet}
/>
</View>
@@ -422,13 +419,13 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
<ResultModalInfo
icon={'faTriangleExclamation'}
iconColor={colors.palette.accent400}
title="Attention!"
title={translate('attention')}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={gotoWallet}
/>
</View>
@@ -440,13 +437,13 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.angry500}
title={resultModalInfo?.title as string || 'Receive failed'}
title={resultModalInfo?.title as string || translate('transactionCommon.receiveFailed')}
message={resultModalInfo?.message as string}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={toggleResultModal}
/>
</View>
+17 -12
View File
@@ -12,6 +12,7 @@ import { Relay } from '../models/Relay'
import AppError, { Err } from '../utils/AppError'
import { log, WalletTask } from '../services'
import { moderateVerticalScale, verticalScale } from '@gocodingnow/rn-size-matters'
import { translate } from '../i18n'
interface SettingsScreenProps extends SettingsStackScreenProps<'Relays'> {}
@@ -70,7 +71,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
const onPastePublicRelay = async function () {
const url = await Clipboard.getString()
if (!url) {
setInfo('Copy your relay URL key first, then paste')
setInfo(translate('relayurlPasteError'))
return
}
setNewPublicRelay(url)
@@ -81,22 +82,26 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
try {
if(newPublicRelay && newPublicRelay.startsWith('wss://')) {
if(relaysStore.alreadyExists(newPublicRelay)) {
setInfo('Relay already exists.')
return
setInfo(translate('relayExists'))
return
}
relaysStore.addRelay({
url: newPublicRelay,
status: WebSocket.CLOSED
url: newPublicRelay,
status: WebSocket.CLOSED
})
toggleAddRelayModal()
onConnect()
} else {
throw new AppError(Err.VALIDATION_ERROR, 'Invalid relay URL.', newPublicRelay)
throw new AppError(
Err.VALIDATION_ERROR,
translate("invalidRelayUrl"),
newPublicRelay
)
}
} catch(e: any) {
handleError(e)
handleError(e)
}
}
@@ -104,7 +109,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
const resetDefaultRelays = function () {
try {
for (const relay of relaysStore.allPublicRelays) {
relaysStore.removeRelay(relay.url)
relaysStore.removeRelay(relay.url)
}
relaysStore.addDefaultRelays()
@@ -205,7 +210,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
<ListItem
leftIcon="faCloudArrowUp"
onPress={onConnect}
text={'Reconnect'}
tx="common.reconnect"
bottomSeparator={true}
style={{paddingHorizontal: spacing.medium}}
/>
@@ -213,7 +218,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
<ListItem
leftIcon="faXmark"
onPress={removeRelay}
text={'Remove relay'}
tx="removeRelay"
style={{paddingHorizontal: spacing.medium}}
/>
</View>
@@ -236,7 +241,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
maxLength={64}
placeholder='wss://...'
selectTextOnFocus={true}
style={[$relayInput, {backgroundColor: inputBg}]}
style={[$relayInput, {backgroundColor: inputBg}]}
/>
<Button
tx={'common.paste'}
@@ -251,7 +256,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
/>
</View>
<View style={[$buttonContainer, {marginTop: spacing.medium}]}>
<Button preset='tertiary' onPress={resetDefaultRelays} text='Reset default'/>
<Button preset='tertiary' onPress={resetDefaultRelays} tx='common.resetDefault' />
<Button preset='tertiary' onPress={toggleAddRelayModal} style={{marginLeft: spacing.small}} text='Cancel'/>
</View>
</View>
+15 -8
View File
@@ -23,6 +23,7 @@ import AppError, { Err } from '../utils/AppError'
import { KeyChain, log, MintClient } from '../services'
import { scale } from '@gocodingnow/rn-size-matters'
import Clipboard from '@react-native-clipboard/clipboard'
import { translate } from '../i18n'
export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> = observer(function RemoteBackupScreen(_props) {
@@ -59,7 +60,10 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
}
if (!validateMnemonic(mnemonic, wordlist)) {
throw new AppError(Err.VALIDATION_ERROR, 'Corrupted or invalid mnemonic. Please move funds and reinstall this wallet.')
throw new AppError(
Err.VALIDATION_ERROR,
translate("backupScreen.invalidMnemonicError")
)
}
setMnemonic(mnemonic)
@@ -79,14 +83,17 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
Clipboard.setString(mnemonic)
if(isNewMnemonic) {
setInfo('To apply backup to your existing funds, send your entire balance to yourself. Otherwise, only funds from this point onward can be restored.')
setInfo(translate("copyMnemonicBackupWorkaround"))
}
return
}
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic.')
throw new AppError(
Err.VALIDATION_ERROR,
translate("backupScreen.missingMnemonicError")
)
} catch (e: any) {
setInfo(`Could not copy: ${e.message}`)
setInfo(translate('common.copyFailParam', { param: e.message }))
}
}
@@ -100,15 +107,15 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
return (
<Screen style={$screen}>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text preset="heading" text="Seed backup" style={{color: 'white'}} />
<Text preset="heading" tx='backupScreen.seedBackup' style={{color: 'white'}} />
</View>
<View style={$contentContainer}>
<Card
style={$card}
ContentComponent={
<ListItem
text='Your mnemonic phrase'
subText='This 12 word sequence represents your seed and allows you to recover your ecash balance in case of device loss. Keep them in safe place.'
tx="backupScreen.mnemonicTitle"
subTx="backupScreen.mnemonicDesc"
leftIcon='faInfoCircle'
leftIconColor={colors.palette.iconYellow300}
leftIconInverse={true}
@@ -147,7 +154,7 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
<Button
preset="default"
style={{margin: spacing.small}}
text="Copy"
tx='common.copy'
onPress={onCopy}
/>
</View>
+83 -70
View File
@@ -44,10 +44,10 @@ import { WalletScreen } from './WalletScreen'
import { MintUnit, formatCurrency, getCurrency } from '../services/wallet/currency'
import { isObj } from '@cashu/cashu-ts/src/utils'
import { WalletProfileRecord } from '../models/WalletProfileStore'
import { translate } from '../i18n'
if (Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
}
const RESTORE_INDEX_INTERVAL = 50
@@ -126,7 +126,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
const maybeMnemonic = await Clipboard.getString()
if(!maybeMnemonic) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic phrase.')
throw new AppError(Err.VALIDATION_ERROR, translate('backupScreen.missingMnemonicError'))
}
const cleanedMnemonic = maybeMnemonic.replace(/\s+/g, ' ').trim()
@@ -140,16 +140,16 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
const onConfirm = async function () {
try {
setStatusMessage('Deriving seed, this takes a while...')
setStatusMessage(translate("derivingSeedStatus"))
if(!mnemonic) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic.')
throw new AppError(Err.VALIDATION_ERROR, translate('backupScreen.missingMnemonicError'))
}
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setIsLoading(true)
if (!validateMnemonic(mnemonic, wordlist)) {
throw new AppError(Err.VALIDATION_ERROR, 'Invalid mnemonic phrase. Provide 12 word sequence separated by blank spaces.')
throw new AppError(Err.VALIDATION_ERROR, translate("recoveryInvalidMnemonicError"))
}
setTimeout(() => {
@@ -160,7 +160,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
setIsLoading(false)
}, 200)
} catch (e: any) {
handleError(e)
handleError(e)
}
}
@@ -209,10 +209,10 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
const startRecovery = async function () {
if(!selectedMintUrl) {
setInfo('Select mint to recover from.')
return
setInfo(translate("recovery.selectMintFrom"))
return
}
setStatusMessage('Starting recovery...')
setStatusMessage(translate("recovery.starting"))
setIsLoading(true)
setTimeout(() => doRecovery(), 100)
}
@@ -231,16 +231,16 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
try {
if(!recoveredMint) {
setInfo('Please select the mint.')
return
setInfo(translate("recovery.noMintSelected"))
return
}
if(!selectedKeyset) {
setInfo('Please select keyset.')
return
setInfo(translate("recovery.noKeysetSelected"))
return
}
setStatusMessage(`Restoring from ${recoveredMint.hostname}...`)
setStatusMessage(translate("recovery.restoringFromParam", { hostname: recoveredMint.hostname }))
log.info('[restore]', `Restoring from ${recoveredMint.hostname}...`)
const { proofs } = await MintClient.restore(
@@ -254,7 +254,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
)
log.debug('[restore]', `Restored proofs`, proofs.length)
setStatusMessage(`Found ${proofs.length} proofs...`)
setStatusMessage(translate("recovery.foundProofsAmount", { amount: proofs.length }))
if (proofs.length > 0) {
// need to move counter by whole interval to avoid duplicate _B!!!
@@ -267,7 +267,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
log.debug('[restore]', `Spent and pending proofs`, {spent: spent.length, pending: pending.length})
setStatusMessage(`${spent.length} proofs were already spent...`)
setStatusMessage(translate("recovery.spentProofsAmount", { amount: spent.length }))
const spentAmount = CashuUtils.getProofsAmount(spent as Proof[])
alreadySpentAmount += spentAmount
@@ -276,7 +276,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
if(unspent && unspent.length > 0) {
setStatusMessage(`Completing recovery...`)
setStatusMessage(translate("recovery.completing"))
const amount = CashuUtils.getProofsAmount(unspent as Proof[])
recoveredAmount = amount
@@ -340,7 +340,8 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
if(pending && pending.length > 0) {
setStatusMessage(`Found ${pending.length} pending proofs...`)
// setStatusMessage(`Found ${pending.length} pending proofs...`)
setStatusMessage(translate("recovery.foundPendingProofsAmount", { amount: pending.length }))
log.debug(`Found pending ecash with ${recoveredMint.hostname}...`)
const amount = CashuUtils.getProofsAmount(pending as Proof[])
@@ -448,25 +449,28 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
const currency = getCurrency(selectedKeyset?.unit as MintUnit)
setResultModalInfo({
status: TransactionStatus.COMPLETED,
message: `${formatCurrency(recoveredAmount, currency.code)} ${currency.code} were recovered into your wallet.`
message: translate("recovery.recoveredResult", {
formattedCurrency: formatCurrency(recoveredAmount, currency.code),
code: currency.code
})
})
} else {
if(errors.length > 0) {
setResultModalInfo({
status: TransactionStatus.ERROR,
message: `Recovery ended with errors.`
message: translate("recovery.resultErrors")
})
setRecoveryErrors(errors)
} else {
if(alreadySpentAmount > 0) {
setResultModalInfo({
status: TransactionStatus.EXPIRED,
message: `Good news is that already spent Ecash has been found. Continue with next recovery interval.`
message: translate("recovery.resultSpent")
})
} else {
setResultModalInfo({
status: TransactionStatus.EXPIRED,
message: `Nothing has been found in this recovery interval.`
message: translate("recovery.resultExpired")
})
}
@@ -480,7 +484,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
const onFindWalletAddress = async () => {
try {
if(!seed || !mnemonic) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic or seed.')
throw new AppError(Err.VALIDATION_ERROR, translate("backupScreen.missingMnemonicOrSeedError"))
}
const seedHash = QuickCrypto.createHash('sha256')
@@ -490,7 +494,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
const currentSeedHash = await KeyChain.loadSeedHash()
if(currentSeedHash === seedHash) {
throw new AppError(Err.VALIDATION_ERROR, 'Can not use seed from this device.')
throw new AppError(Err.VALIDATION_ERROR, translate("recovery.seedFromCurrentDeviceError"))
}
const profile = await MinibitsClient.getWalletProfileBySeedHash(seedHash as string)
@@ -502,11 +506,11 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
if(profile.nip05.includes(MINIBITS_NIP05_DOMAIN)) {
setProfileToRecover(profile)
} else {
setInfo(`You used wallet address ${profile.nip05} with your own keys, import it again.`)
setInfo(translate("recovery.ownKeysImportAgain", { addr: profile.nip05 }))
await delay(5000)
}
} else {
setInfo('Could not find wallet address linked to your seed on the server.')
setInfo(translate("recovery.noWalletForSeedError"))
}
} catch (e: any) {
handleError(e)
@@ -517,10 +521,10 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
const onCompleteAddress = async () => {
try {
if(!seed || !mnemonic || !profileToRecover) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic or seed or profile to recover.')
throw new AppError(Err.VALIDATION_ERROR, translate("recovery.missingMnemonicSeedProfileError"))
}
setStatusMessage('Recovering wallet address...')
setStatusMessage(translate("recovery.recoveringAddress"))
setIsLoading(true)
const seedHash = QuickCrypto.createHash('sha256')
@@ -539,7 +543,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
await KeyChain.saveSeed(seed as Uint8Array)
await delay(1000)
setStatusMessage(`Recovery completed`)
setStatusMessage(translate("recovery.completed"))
await delay(2000)
@@ -556,10 +560,10 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
const onComplete = async () => {
try {
if(!seed || !mnemonic) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic or seed.')
throw new AppError(Err.VALIDATION_ERROR, translate('backupScreen.missingMnemonicOrSeedError'))
}
setStatusMessage('Recovering wallet address...')
setStatusMessage(translate('recovery.recoveringAddress'))
setIsLoading(true)
await KeyChain.saveMnemonic(mnemonic)
@@ -574,7 +578,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
// Skip external profiles beacause we do not control keys
if(profileToRecover) {
log.info('[onComplete] recovery', {profileToRecover})
setStatusMessage(`Found ${profileToRecover.nip05}`)
setStatusMessage(translate("recovery.foundAddrParam", { addr: profileToRecover.nip05 }))
if(profileToRecover.nip05.includes(MINIBITS_NIP05_DOMAIN)) {
const {publicKey: newPublicKey} = await NostrClient.getOrCreateKeyPair()
@@ -583,11 +587,11 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
// Align walletId in userSettings with recovered profile
userSettingsStore.setWalletId(walletProfileStore.walletId)
await delay(1000)
setStatusMessage(`Recovery completed`)
setStatusMessage(translate('recovery.completed'))
await delay(2000)
} else {
setInfo(`You used wallet address ${profileToRecover.nip05} with your own keys, import it again.`)
await delay(5000)
setInfo(translate("recovery.ownKeysImportAgain", { addr: profileToRecover.nip05 }))
await delay(5000)
}
}
@@ -623,8 +627,8 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
style={$card}
ContentComponent={
<ListItem
text='Mnemonic exists'
subText='Your wallet already has another mnemonic in its secure storage. Recovery process works only with freshly installed wallet to avoid loss of your funds.'
tx="recovery.mnemonicCollision"
subTx="recovery.mnemonicCollisionDesc"
leftIcon='faTriangleExclamation'
// leftIconColor='red'
style={$item}
@@ -635,7 +639,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<View style={$buttonContainer}>
<Button
onPress={onBack}
text='Back'
tx='common.back'
preset='secondary'
/>
</View>
@@ -649,7 +653,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
style={$card}
ContentComponent={
<ListItem
text='Your mnemonic phrase'
tx='backupScreen.mnemonicTitle'
subText={mnemonic}
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='1'/></View>}
style={$item}
@@ -665,7 +669,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
ContentComponent={
<ListItem
text={profileToRecover.nip05}
subText={'This is the wallet address linked to your seed. If you press Complete, your wallet profile will reset to above seed and address.'}
subTx="profileToRecoverDesc"
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='2'/></View>}
style={$item}
/>
@@ -674,7 +678,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<View style={$buttonContainer}>
<Button
onPress={onCompleteAddress}
text={'Complete recovery'}
tx="recovery.completeCTA"
style={{marginRight: spacing.small}}
/>
</View>
@@ -683,7 +687,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<View style={$buttonContainer}>
<Button
onPress={onFindWalletAddress}
text={'Find wallet address'}
tx="recovery.findAddressCTA"
style={{marginRight: spacing.small}}
/>
</View>
@@ -696,8 +700,8 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
HeadingComponent={
<>
<ListItem
text='Recovery from mints'
subText='Identify mints to recover your ecash from and add them to the list.'
tx="recoveryFromMints"
subTx="recoveryFromMintsDesc"
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='2'/></View>}
RightComponent={mintsStore.mintCount > 0 ? (
<View style={$rightContainer}>
@@ -715,7 +719,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<View style={$buttonContainer}>
<Button
onPress={onAddMints}
text='Add mints'
tx="addMints"
/>
</View>
)}
@@ -744,13 +748,13 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
{(startIndex > 0 || totalRecoveredAmount > 0) && (
<Button
onPress={onComplete}
text={'Complete'}
tx="common.complete"
style={{marginRight: spacing.small}}
/>
)}
<Button
onPress={startRecovery}
text={startIndex === 0 ? 'Start recovery' : 'Next interval'}
tx={startIndex === 0 ? 'startRecovery' : 'nextInterval'}
preset={(startIndex === 0 || totalRecoveredAmount > 0) ? 'default' : 'secondary'}
disabled={selectedMintUrl ? false : true}
/>
@@ -758,13 +762,16 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<View style={$buttonContainer}>
<Text
text={`Recovery interval ${startIndex} - ${endIndex} · `}
text={translate("recovery.intervalParam", {
startIndex: startIndex,
endIndex: endIndex
})}
size='xxs'
style={{color: textHint, alignSelf: 'center', marginTop: spacing.small}}
/>
<Pressable onPress={toggleIndexModal}>
<Text
text={`Set manually`}
tx="recovery.setManually"
size='xxs'
style={{color: textHint, alignSelf: 'center', marginTop: spacing.small}}
/>
@@ -772,14 +779,17 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
</View>
<View style={[$buttonContainer,{marginTop: 0}]}>
<Text
text={`Keyset ID ${selectedKeyset?.id} (${selectedKeyset?.unit})`}
text={translate("recovery.keysetID", {
id: selectedKeyset?.id,
unit: selectedKeyset?.unit
})}
size='xxs'
style={{color: textHint, alignSelf: 'center', marginTop: spacing.extraSmall}}
/>
{selectedMintKeysets.length > 1 && (
<Pressable onPress={toggleKeysetModal}>
<Text
text={` · Select another`}
tx="recovery.selectAnotherKeyset"
size='xxs'
style={{color: textHint, alignSelf: 'center', marginTop: spacing.extraSmall}}
/>
@@ -790,7 +800,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
)}
{mintsStore.mintCount > 0 && !selectedMintUrl && (
<Text
text={`Select mint to recover from`}
tx='recovery.selectMintFrom'
size='xxs'
style={{color: textHint, alignSelf: 'center', margin: spacing.large}}
/>
@@ -806,8 +816,11 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
style={$card}
ContentComponent={
<ListItem
text='Insert mnemonic phrase'
subText={`Paste or rewrite 12 word phrase to recover your ${isAddressOnlyRecovery ? 'wallet address' : 'ecash balance and wallet address'} on this device. Separate words by blank spaces.`}
tx="recoveryInsertMnemonic"
subTx={isAddressOnlyRecovery
? 'recoveryInsertMnemonicDescAddrOnly'
: 'recoveryInsertMnemonicDesc'
}
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='1'/></View>}
style={$item}
/>
@@ -823,7 +836,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
autoCapitalize='none'
keyboardType='default'
maxLength={150}
placeholder='Mnemonic phrase...'
placeholder={translate("mnemonicPhrasePlaceholder")}
selectTextOnFocus={true}
style={[$mnemonicInput, {backgroundColor: inputBg, flexWrap: 'wrap'}]}
/>
@@ -831,12 +844,12 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
{mnemonic ? (
<Button
onPress={onConfirm}
text='Confirm'
tx='common.confirm'
/>
) : (
<Button
onPress={onPaste}
text='Paste'
tx='common.paste'
/>
)
}
@@ -852,7 +865,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
isVisible={isIndexModalVisible}
ContentComponent={
<View style={$indexContainer}>
<Text text="Set start index" preset="subheading" />
<Text tx="setStartIndex" preset="subheading" />
<View style={{flexDirection: 'row', alignItems: 'center'}}>
<TextInput
ref={indexInputRef}
@@ -865,12 +878,12 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
textAlign='right'
/>
<Button
text="Save"
tx='common.save'
onPress={onResetStartIndex}
/>
</View>
<Text
text={`Use to increase starting index of recovery interval e.g. in case of repeated recovery. You need to know at what index you completed your previous recovery or you'll miss ecash to recover.`}
tx="recovery.startIndexDesc"
size='xxs'
style={{color: textHint, margin: spacing.small}}
/>
@@ -882,7 +895,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<BottomModal
isVisible={isKeysetModalVisible}
// style={{alignItems: 'stretch'}}
HeadingComponent={<Text text="Select keyset to be used for recovery" style={{textAlign: 'center', margin: spacing.small}}/>}
HeadingComponent={<Text tx="recovery.selectKeyset" style={{textAlign: 'center', margin: spacing.small}}/>}
ContentComponent={
<FlatList
data={selectedMintKeysets}
@@ -911,7 +924,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<Button
preset={'secondary'}
onPress={toggleKeysetModal}
text={`Close`}
tx='common.close'
style={{marginTop: spacing.small}}
/>}
onBackButtonPress={toggleKeysetModal}
@@ -949,13 +962,13 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<ResultModalInfo
icon="faCheckCircle"
iconColor={colors.palette.success200}
title="Recovery success!"
title={translate("recovery.success")}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={toggleResultModal}
/>
</View>
@@ -967,13 +980,13 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.angry500}
title="Recovery failed"
title={translate("recovery.failed")}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
text={'Show errors'}
tx="showErrors"
onPress={toggleErrorsModal}
/>
</View>
@@ -985,13 +998,13 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
<ResultModalInfo
icon='faInfoCircle'
iconColor={colors.palette.neutral400}
title="No ecash recovered"
title={translate("noEcashRecovered")}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={toggleResultModal}
/>
</View>
+9 -8
View File
@@ -20,6 +20,7 @@ import { LnurlUtils } from '../services/lnurl/lnurlUtils'
import { infoMessage } from '../utils/utils'
import Clipboard from '@react-native-clipboard/clipboard'
import { useStores } from '../models'
import { translate } from '../i18n'
const hasAndroidCameraPermission = async () => {
const cameraPermission = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA)
@@ -113,7 +114,7 @@ export const ScanScreen: FC<WalletStackScreenProps<'Scan'>> = function ScanScree
}
e.params = incoming
e.message = 'Could not extract ecash token nor LNURL withdraw link to receive.'
e.message = translate("scanReceiveExtractFail")
handleError(e)
break
}
@@ -170,12 +171,12 @@ export const ScanScreen: FC<WalletStackScreenProps<'Scan'>> = function ScanScree
default:
try {
// generic scan button on wallet screen
const incomingData = IncomingParser.findAndExtract(incoming)
return IncomingParser.navigateWithIncomingData(incomingData, navigation, unit)
const incomingData = IncomingParser.findAndExtract(incoming)
return IncomingParser.navigateWithIncomingData(incomingData, navigation, unit)
} catch (e: any) {
e.name = Err.VALIDATION_ERROR
e.params = {caller: 'onIncomingData', clipboard: incoming.slice(0, 100)}
handleError(e)
e.name = Err.VALIDATION_ERROR
e.params = {caller: 'onIncomingData', clipboard: incoming.slice(0, 100)}
handleError(e)
}
}
@@ -184,7 +185,7 @@ export const ScanScreen: FC<WalletStackScreenProps<'Scan'>> = function ScanScree
const onPaste = async function() {
const clipboard = await Clipboard.getString()
if (clipboard.length === 0) {
infoMessage('First copy ecash token, invoice, LNURL link or lightning address. Then paste.')
infoMessage(translate("scanScreen.onPasteEmptyClipboard"))
return
}
@@ -215,7 +216,7 @@ export const ScanScreen: FC<WalletStackScreenProps<'Scan'>> = function ScanScree
LeftAccessory={() => (
<Icon icon='faPaste'/>
)}
text={'Paste'}
tx='common.paste'
preset='secondary'
style={{marginTop: spacing.medium, minWidth: 120}}
/>
+3 -3
View File
@@ -333,14 +333,14 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
log.trace('[onAmountEndEditing]', amount)
if (!amount || amount === 0) {
infoMessage('Amount should be positive number.')
infoMessage(translate('payCommon.amountZeroOrNegative'))
return
}
const availableBalances = proofsStore.getMintBalancesWithEnoughBalance(amount, unit)
if (availableBalances.length === 0) {
infoMessage('There are not enough funds to send this amount.')
infoMessage(translate('payCommon.insufficientFunds'))
return
}
@@ -684,7 +684,7 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
/>
{transaction?.memo && (
<TranItem
label="tranDetailScreen.memoToReceiver"
label="receiverMemo"
value={transaction.memo as string}
/>
)}
+5 -5
View File
@@ -178,7 +178,7 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
ContentComponent={
<>
<ListItem
tx='settingsScreen.manageMints'
tx='manageMints'
leftIcon='faCoins'
leftIconColor={colors.palette.iconBlue300}
leftIconInverse={true}
@@ -219,7 +219,7 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
ContentComponent={
<>
<ListItem
text='Push notifications'
tx="pushNotifications"
subText={`Token: ${walletProfileStore.device?.slice(0, 10)}...`}
leftIcon='faPaperPlane'
leftIconColor={colors.palette.green400}
@@ -229,7 +229,7 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
<View style={$rightContainer}>
<Text
style={$itemRight}
text={areNotificationsEnabled ? 'Enabled' : 'Disabled'}
tx={areNotificationsEnabled ? 'common.enabled' : 'common.disabled'}
/>
</View>
}
@@ -237,8 +237,8 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
onPress={openNotificationSettings}
/>
<ListItem
text={'Nostr relays'}
subText={`Connected: ${relaysStore.connectedCount}`}
tx="nostr.relaysTitle"
subText={translate("common.connectedParam", { param: relaysStore.connectedCount })}
leftIcon='faCircleNodes'
leftIconColor={colors.palette.iconViolet200}
leftIconInverse={true}
+10 -9
View File
@@ -21,6 +21,7 @@ import { useStores } from '../models'
import { Mint } from '../models/Mint'
import { MintHeader } from './Mints/MintHeader'
import { moderateVerticalScale } from '@gocodingnow/rn-size-matters'
import { translate } from '../i18n'
export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = function TokenReceiveScreen(_props) {
@@ -46,7 +47,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
log.trace('[TokenReceiveScreen.setUnitAndMint]')
const {unit, mintUrl} = route.params
if(!unit) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint unit in route params')
throw new AppError(Err.VALIDATION_ERROR, translate("missingMintUnitRouteParamsError"))
}
setUnit(unit)
@@ -74,7 +75,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
const onPaste = async function() {
const clipboard = await Clipboard.getString()
if (clipboard.length === 0) {
infoMessage('First copy ecash token, then paste.')
infoMessage(translate('tokenReceiveScreen.onPasteEmptyClipboard'))
return
}
@@ -98,7 +99,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
const onConfirm = async function() {
if(!encodedToken) {
setError({name: Err.VALIDATION_ERROR, message: 'Missing ecash token to receive.'})
setError({name: Err.VALIDATION_ERROR, message: translate("missingEcashTokenToReceiveError")})
return
}
@@ -143,7 +144,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text
preset="heading"
text={'Receive ecash'}
tx="payCommon.receiveEcash"
style={{color: 'white'}}
/>
</View>
@@ -153,7 +154,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
<ListItem
leftIcon='faMoneyBill1'
leftIconColor={colors.palette.iconViolet300}
text='Paste ecash token'
tx="common.pasteEcashToken"
bottomSeparator={true}
/* RightComponent={
<Button
@@ -171,7 +172,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
<Text
size='xs'
style={{color: hintText, padding: spacing.extraSmall}}
text='Paste ecash token you want to receive.'
tx="pasteEcashTokenDesc"
/>
<View style={{alignItems: 'center', marginTop: spacing.small}}>
<TextInput
@@ -191,7 +192,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
<View style={$buttonContainer}>
<Button
preset='default'
tx={'common.confirm'}
tx='common.confirm'
onPress={onConfirm}
style={{marginLeft: spacing.small}}
LeftAccessory={() => <Icon icon='faCheckCircle' color='white'/>}
@@ -209,7 +210,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
/>
<Button
preset='secondary'
tx={'common.scan'}
tx='common.scan'
onPress={gotoScan}
style={{marginLeft: spacing.small}}
LeftAccessory={() => {
@@ -230,7 +231,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
}
/>
<Button
text={'Topup with Lightning'}
tx="tokenReceiveScreen.topupWithLightning"
LeftAccessory={() => (
<Icon
icon='faBolt'
File diff suppressed because it is too large Load Diff
+64 -66
View File
@@ -175,25 +175,25 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
try {
Clipboard.setString(JSON.stringify(getAuditTrail(transaction)))
} catch (e: any) {
setInfo(`Could not copy: ${e.message}`)
setInfo(translate("common.copyFailParam", { param: e.message }))
}
}
const copyToken = function (transaction: Transaction) {
try {
const encoded = getEncodedTokenToSend(transaction)
const encoded = getEncodedTokenToSend(transaction)
if (!encoded) {
if (!encoded) {
throw new AppError(
Err.VALIDATION_ERROR,
'Could not get encoded ecash token from transaction',
Err.VALIDATION_ERROR,
'Could not get encoded ecash token from transaction',
)
}
}
Clipboard.setString(encoded)
Clipboard.setString(encoded)
} catch (e: any) {
setInfo(`Could not copy: ${e.message}`)
setInfo(translate("common.copyFailParam", { param: e.message }))
}
}
@@ -202,7 +202,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
try {
Clipboard.setString(JSON.stringify(proofsByStatus))
} catch (e: any) {
setInfo(`Could not copy: ${e.message}`)
setInfo(translate("common.copyFailParam", { param: e.message }))
}
}
@@ -222,9 +222,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
const getFormattedAmount = function(): string {
if (!transaction) {
return ''
}
if (!transaction) { return '' }
switch (transaction?.type) {
case TransactionType.RECEIVE || TransactionType.RECEIVE_OFFLINE:
@@ -274,7 +272,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
? transaction.noteToSelf
: savedNote
? savedNote
: 'Add your note'
: translate("tranDetailScreen.addYourNote")
}
LeftComponent={
<Icon
@@ -289,7 +287,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
onPress={toggleNoteModal}
/>
<ListItem
text="Add tags"
tx="tranDetailScreen.addTags"
LeftComponent={
<Icon
containerStyle={$iconContainer}
@@ -357,7 +355,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
<>
<Text
style={{color: labelColor, fontSize: 14}}
text="Backed up ecash"
tx="tranDetailScreen.backedUpEcash"
/>
<JSONTree
hideRoot
@@ -374,7 +372,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
<Button
preset="tertiary"
onPress={() => copyBackupProofs(proofsByStatus)}
text="Copy"
tx="common.copy"
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
@@ -393,7 +391,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
isVisible={isNoteModalVisible}
ContentComponent={
<View style={$noteContainer}>
<Text text="Add your note" preset="subheading" />
<Text tx="tranDetailScreen.addYourNote" preset="subheading" />
<View style={{flexDirection: 'row', alignItems: 'center'}}>
<TextInput
ref={noteInputRef}
@@ -403,7 +401,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
maxLength={200}
/>
<Button
text="Save"
tx="common.save"
onPress={saveNote}
/>
</View>
@@ -459,7 +457,7 @@ const ReceiveInfoBlock = function (props: {
const transactionDataUpdate = {
status: TransactionStatus.EXPIRED,
message: 'Ecash has been successfully received after retry. New transaction has been created.',
message: translate("tranDetailScreen.successRetrieveEcashAfterRetry"),
createdAt: new Date(),
}
@@ -567,7 +565,7 @@ const ReceiveInfoBlock = function (props: {
<Button
style={{marginTop: spacing.medium}}
// preset="secondary"
text="Retry to receive"
tx="tranDetailScreen.retryToReceive"
onPress={onRetryToReceive}
/>
</View>
@@ -597,7 +595,7 @@ const ReceiveInfoBlock = function (props: {
style={$dataCard}
ContentComponent={
<TranItem
label="tranDetailScreen.receivedTo"
label="transactionCommon.receivedTo"
value={transaction.mint as string}
/>
}
@@ -610,7 +608,7 @@ const ReceiveInfoBlock = function (props: {
<>
<Text
style={{color: labelColor, fontSize: 14}}
text="Audit trail"
tx="tranDetailScreen.auditTrail"
/>
<JSONTree
hideRoot
@@ -649,7 +647,7 @@ const ReceiveInfoBlock = function (props: {
<ResultModalInfo
icon="faCheckCircle"
iconColor={colors.palette.success200}
title="Success!"
title={translate("tranDetailScreen.modalSuccess")}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
@@ -667,13 +665,13 @@ const ReceiveInfoBlock = function (props: {
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.angry500}
title="Receive failed"
title={translate('transactionCommon.receiveFailed')}
message={resultModalInfo?.message as string}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={toggleResultModal}
/>
</View>
@@ -793,7 +791,7 @@ const ReceiveOfflineInfoBlock = function (props: {
) : (
<TranItem
label="tranDetailScreen.amount"
value={`${transaction.amount}`}
value={transaction.amount}
isFirst={true}
/>
)}
@@ -833,7 +831,7 @@ const ReceiveOfflineInfoBlock = function (props: {
style={$dataCard}
ContentComponent={
<TranItem
label="tranDetailScreen.receivedTo"
label="transactionCommon.receivedTo"
value={transaction.mint as string}
/>
}
@@ -846,7 +844,7 @@ const ReceiveOfflineInfoBlock = function (props: {
<>
<Text
style={{color: labelColor, fontSize: 14}}
text="Audit trail"
tx='tranDetailScreen.auditTrail'
/>
<JSONTree
hideRoot
@@ -886,13 +884,13 @@ const ReceiveOfflineInfoBlock = function (props: {
<ResultModalInfo
icon="faCheckCircle"
iconColor={colors.palette.success200}
title="Success!"
title={translate('common.success')}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={onGoBack}
/>
</View>
@@ -904,13 +902,13 @@ const ReceiveOfflineInfoBlock = function (props: {
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.angry500}
title="Receive failed"
title={translate('transactionCommon.receiveFailed')}
message={resultModalInfo?.message as string}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={onGoBack}
/>
</View>
@@ -950,7 +948,7 @@ const SendInfoBlock = function (props: {
isFirst={true}
/>
<TranItem
label="tranDetailScreen.memoToReceiver"
label="receiverMemo"
value={transaction.memo as string}
/>
{transaction.sentTo && (
@@ -1025,7 +1023,7 @@ const SendInfoBlock = function (props: {
fontSize: 14,
marginTop: spacing.small,
}}
text="Pending token"
tx="tranDetailScreen.pendingToken"
/>
<Text
text={getEncodedTokenToSend(transaction) as string}
@@ -1041,7 +1039,7 @@ const SendInfoBlock = function (props: {
<Button
preset="tertiary"
onPress={() => copyToken(transaction)}
text="Copy"
tx='common.copy'
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
@@ -1060,7 +1058,7 @@ const SendInfoBlock = function (props: {
<>
<Text
style={{color: labelColor, fontSize: 14}}
text="Audit trail"
tx='tranDetailScreen.auditTrail'
/>
<JSONTree
hideRoot
@@ -1078,7 +1076,7 @@ const SendInfoBlock = function (props: {
<Button
preset="tertiary"
onPress={() => copyAuditTrail(transaction)}
text="Copy"
tx='common.copy'
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
@@ -1195,7 +1193,7 @@ const TopupInfoBlock = function (props: {
isFirst={true}
/>
<TranItem
label="tranDetailScreen.memoToReceiver"
label="receiverMemo"
value={transaction.memo as string}
/>
{transaction.sentFrom && (
@@ -1218,7 +1216,7 @@ const TopupInfoBlock = function (props: {
<Button
style={{marginTop: spacing.medium}}
preset="secondary"
text="Retry to complete"
tx="tranDetailScreen.retryToComplete"
onPress={onRetryToHandlePendingTopup}
/>
</View>
@@ -1254,10 +1252,10 @@ const TopupInfoBlock = function (props: {
/>
{transaction.status === TransactionStatus.PENDING && paymentRequest && (
<>
<Text style={{color: labelColor, marginTop: spacing.medium}} text='Invoice to pay'/>
<View style={$qrCodeContainer}>
<QRCode size={270} value={paymentRequest.encodedInvoice} />
</View>
<Text style={{ color: labelColor, marginTop: spacing.medium }} tx="tranDetailScreen.invoiceToPay" />
<View style={$qrCodeContainer}>
<QRCode size={270} value={paymentRequest.encodedInvoice} />
</View>
</>
)}
</>
@@ -1266,16 +1264,16 @@ const TopupInfoBlock = function (props: {
<>
{transaction.status === TransactionStatus.PENDING && paymentRequest && (
<Button
preset="tertiary"
onPress={() => copyInvoice()}
text="Copy"
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
marginTop: spacing.small,
alignSelf: 'center',
}}
textStyle={{fontSize: 14}}
preset="tertiary"
onPress={() => copyInvoice()}
tx='common.copy'
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
marginTop: spacing.small,
alignSelf: 'center',
}}
textStyle={{fontSize: 14}}
/>
)}
</>
@@ -1289,7 +1287,7 @@ const TopupInfoBlock = function (props: {
<>
<Text
style={{color: labelColor, fontSize: 14}}
text="Audit trail"
tx="tranDetailScreen.auditTrail"
/>
<JSONTree
hideRoot
@@ -1306,7 +1304,7 @@ const TopupInfoBlock = function (props: {
<Button
preset="tertiary"
onPress={() => copyAuditTrail(transaction)}
text="Copy"
tx="common.copy"
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
@@ -1328,13 +1326,13 @@ const TopupInfoBlock = function (props: {
<ResultModalInfo
icon="faCheckCircle"
iconColor={colors.palette.success200}
title="Success!"
title={translate("tranDetailScreen.modalSuccess")}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={onGoBack}
/>
</View>
@@ -1345,7 +1343,7 @@ const TopupInfoBlock = function (props: {
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.accent400}
title="Invoice not paid"
title={translate("tranDetailScreen.invoiceNotPaid")}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
@@ -1362,13 +1360,13 @@ const TopupInfoBlock = function (props: {
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.accent400}
title="Invoice expired"
title={translate("tranDetailScreen.invoiceExpired")}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={toggleResultModal}
/>
</View>
@@ -1379,13 +1377,13 @@ const TopupInfoBlock = function (props: {
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.angry500}
title="Topup error"
title={translate("tranDetailScreen.topupError")}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
tx='common.close'
onPress={toggleResultModal}
/>
</View>
@@ -1471,8 +1469,8 @@ const TransferInfoBlock = function (props: {
style={$dataCard}
ContentComponent={
<TranItem
label="tranDetailScreen.paidFrom"
value={transaction.mint as string}
label="transactionCommon.paidFrom"
value={transaction.mint as string}
/>
}
/>
@@ -1484,7 +1482,7 @@ const TransferInfoBlock = function (props: {
<>
<Text
style={{color: labelColor, fontSize: 14}}
text="Audit trail"
tx="tranDetailScreen.auditTrail"
/>
<JSONTree
hideRoot
@@ -1502,7 +1500,7 @@ const TransferInfoBlock = function (props: {
<Button
preset="tertiary"
onPress={() => copyAuditTrail(transaction)}
text="Copy"
tx="common.copy"
style={{
minHeight: 25,
paddingVertical: spacing.extraSmall,
+28 -12
View File
@@ -33,10 +33,11 @@ import AppError from '../utils/AppError'
import {TransactionListItem} from './Transactions/TransactionListItem'
import { Transaction, TransactionStatus } from '../models/Transaction'
import { height } from '@fortawesome/free-solid-svg-icons/faWallet'
import { translate } from '../i18n'
if (Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
}
// Number of transactions held in TransactionsStore model
const limit = maxTransactionsInModel
@@ -248,7 +249,16 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
ContentComponent={
<>
<ListItem
text={showPendingOnly ? `Showing ${transactionsStore.pending.length} of ${pendingDbCount} pending` : `Showing ${transactionsStore.count} of ${dbCount} total`}
text={showPendingOnly
? translate("tranHistory.showingPaginationPending", {
amount: transactionsStore.pending.length,
total: pendingDbCount
})
: translate("tranHistory.showingPaginationTotal", {
amount: transactionsStore.count,
total: dbCount
})
}
LeftComponent={
<Icon
containerStyle={$iconContainer}
@@ -262,7 +272,9 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
onPress={() => false}
/>
<ListItem
text={`${transactionsStore.pending.length} Pending transactions`}
text={translate("tranHistory.pendingParam", {
param: transactionsStore.pending.length
})}
LeftComponent={
<Icon
containerStyle={$iconContainer}
@@ -308,12 +320,12 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
{sections.length > 0 && (
<View style={{alignItems: 'center', marginTop: spacing.small}}>
{!showPendingOnly && isAll || showPendingOnly && pendingIsAll ? (
<Text text="List is complete" size="xs" />
<Text tx="tranHistory.listIsComplete" size="xs" />
) : (
<Button
preset="secondary"
onPress={getTransactionsList}
text="View more"
tx="tranHistory.viewMore"
style={{minHeight: 25, paddingVertical: spacing.tiny}}
textStyle={{fontSize: 14}}
/>
@@ -329,8 +341,8 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
<Card
ContentComponent={
<ListItem
leftIcon='faBan'
text={"No transactions to show."}
leftIcon='faBan'
tx={"tranHistory.noTransToShow"}
/>
}
style={$card}
@@ -346,15 +358,19 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
ContentComponent={
<>
<ListItem
text='Delete expired'
subText={`This will delete ${expiredDbCount} expired transactions`}
tx="tranHistory.deleteExpired"
subText={translate("tranHistory.deleteExpiredDesc", {
count: expiredDbCount
})}
leftIcon='faRotate'
onPress={onDeleteExpired}
bottomSeparator={true}
/>
<ListItem
text="Delete with errors"
subText={`This will delete ${erroredDbCount} transactions with errors`}
tx="tranHistory.deleteErrored"
subText={translate("tranHistory.deleteErroredDesc", {
count: erroredDbCount
})}
leftIcon='faBug'
onPress={onDeleteErrored}
/>
+74 -72
View File
@@ -50,6 +50,7 @@ import { MintBalanceSelector } from './Mints/MintBalanceSelector'
import numbro from 'numbro'
import { TranItem } from './TranDetailScreen'
import useIsInternetReachable from '../utils/useIsInternetReachable'
import { translate } from '../i18n'
if (
@@ -116,7 +117,7 @@ useEffect(() => {
try {
const {unit, mintUrl} = route.params
if(!unit) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint unit in route params')
throw new AppError(Err.VALIDATION_ERROR, translate('missingMintUnitRouteParamsError'))
}
setUnit(unit)
@@ -180,7 +181,7 @@ useFocusEffect(
const {lnurlParams} = route.params
if (!lnurlParams) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing LNURL params.')
throw new AppError(Err.VALIDATION_ERROR, translate('missingLNURLParamsError'))
}
const metadata = lnurlParams.decodedMetadata
@@ -291,7 +292,10 @@ useEffect(() => {
let availableBalances = proofsStore.getMintBalancesWithEnoughBalance(totalAmount, unit)
if (availableBalances.length === 0) {
infoMessage(`There is not enough balance in ${getCurrency(unit).code} to pay the invoice amount and expected fees: ${amountToTransfer} ${getCurrency(unit).code}`)
infoMessage(translate("transferScreen.insufficientFunds", {
currency: getCurrency(unit).code,
amount: amountToTransfer
}))
return
}
@@ -347,7 +351,7 @@ useEffect(() => {
} else {
setResultModalInfo({
status,
title: error.params?.message ? error.message : 'Payment failed',
title: error.params?.message ? error.message : translate('payCommon.failed'),
message: error.params?.message || error.message,
})
}
@@ -418,50 +422,51 @@ const onMintBalanceSelect = function (balance: MintBalance) {
// Amount is editable only in case of LNURL Pay, while invoice is not yet retrieved
const onAmountEndEditing = async function () {
try {
const precision = getCurrency(unit).precision
const mantissa = getCurrency(unit).mantissa
const amount = round(toNumber(amountToTransfer) * precision, 0)
try {
const precision = getCurrency(unit).precision
const mantissa = getCurrency(unit).mantissa
const amount = round(toNumber(amountToTransfer) * precision, 0)
if (!amount || amount === 0) {
infoMessage('Amount should be positive number.')
return
}
if(!lnurlPayParams) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing LNURL pay parameters', {caller: 'onAmountEndEditing'})
}
if (lnurlPayParams.minSendable && amount < lnurlPayParams.minSendable / 1000 ) {
infoMessage(`Minimal amount to pay is ${roundUp(lnurlPayParams.minSendable / 1000, 0)} ${CurrencyCode.SATS}.`)
return
}
if (lnurlPayParams.maxSendable && amount > lnurlPayParams.maxSendable / 1000 ) {
infoMessage(`Maximal amount to pay is ${roundDown(lnurlPayParams.maxSendable / 1000, 0)} ${CurrencyCode.SATS}.`)
return
}
if (lnurlPayParams.payerData) {
infoMessage(`Minibits does not yet support entering of payer identity data (LUD18).`)
}
setAmountToTransfer(`${numbro(amountToTransfer).format({thousandSeparated: true, mantissa: getCurrency(unit).mantissa})}`)
setIsLoading(true)
const encoded = await LnurlClient.getInvoice(lnurlPayParams, amount * 1000)
setIsLoading(false)
if(encoded) {
return onEncodedInvoice(encoded)
}
throw new AppError(Err.NOTFOUND_ERROR, `Could not get lightning invoice from ${lnurlPayParams.domain}`)
} catch (e: any) {
handleError(e)
if (!amount || amount === 0) {
infoMessage(translate('payCommon.amountZeroOrNegative'))
return;
}
}
if(!lnurlPayParams) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing LNURL pay parameters', {caller: 'onAmountEndEditing'})
}
if (lnurlPayParams.minSendable && amount < lnurlPayParams.minSendable / 1000) {
infoMessage(translate('payCommon.minimumWithdraw', {
amount: roundUp(lnurlPayParams.minSendable / 1000, 0),
currency: CurrencyCode.SATS
}))
return;
}
if (lnurlPayParams.maxSendable && amount > lnurlPayParams.maxSendable / 1000 ) {
infoMessage(translate("payCommon.maximumPay", {
amount: roundDown(lnurlPayParams.maxSendable / 1000, 0),
currency: CurrencyCode.SATS
}))
return;
}
if (lnurlPayParams.payerData) {
infoMessage(translate("transferScreen.LUD18unsupported"))
}
setAmountToTransfer(`${numbro(amountToTransfer).format({thousandSeparated: true, mantissa: getCurrency(unit).mantissa})}`)
setIsLoading(true)
const encoded = await LnurlClient.getInvoice(lnurlPayParams, amount * 1000)
setIsLoading(false)
if (encoded) return onEncodedInvoice(encoded);
throw new AppError(Err.NOTFOUND_ERROR, `Could not get lightning invoice from ${lnurlPayParams.domain}`)
} catch (e: any) { handleError(e) }
}
const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: string = '') {
@@ -479,23 +484,20 @@ const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: st
log.trace('[onEncodedInvoice] Invoice data', {amount, expiresAt, description})
if (!amount || amount === 0) {
infoMessage('Invoice amount should be positive number')
return
infoMessage(translate('payCommon.amountZeroOrNegative'))
return;
}
if(!isInternetReachable) {
setInfo('Your device is currently offline.')
}
if(!isInternetReachable) setInfo(translate('common.offlinePretty'));
setEncodedInvoice(encoded)
setInvoice(invoice)
setInvoiceExpiry(expiresAt)
if (paymentRequestDesc) {
setMemo(paymentRequestDesc)
setMemo(paymentRequestDesc)
} else if(description) {
setMemo(description)
setMemo(description)
}
// We need to retrieve the quote first to know how much is needed to settle invoice in selected currency unit
@@ -505,29 +507,29 @@ const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: st
proofsStore.getMintBalancesWithUnit(unit)[0]
if (!balanceToTransferFrom) {
infoMessage(`There is no mint with ${unit} balance from which payment can be made.`)
return
infoMessage(translate("transferScreen.noMintWithBalance", { unit }))
return
}
setMintBalanceToTransferFrom(balanceToTransferFrom)
// continues in hook that handles other mint selection by user
} catch (e: any) {
resetState()
handleError(e)
navigation.popToTop()
resetState()
handleError(e)
navigation.popToTop()
}
}
const transfer = async function () {
try {
if(!meltQuote) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing quote to initiate transfer transaction')
throw new AppError(Err.VALIDATION_ERROR, 'Missing quote to initiate transfer transaction')
}
if (!mintBalanceToTransferFrom) {
setInfo('Select mint balance to transfer from')
return
setInfo(translate("transferScreen.selectMintFrom"))
return;
}
setIsLoading(true)
@@ -601,7 +603,7 @@ const satsColor = colors.palette.primary200
) : (
<Text
size='sm'
text={'Amount to pay'}
tx="payCommon.amountToPayLabel"
style={{color: 'white', textAlign: 'center'}}
/>
)}
@@ -613,7 +615,7 @@ const satsColor = colors.palette.primary200
style={[$card, {minHeight: 50}]}
ContentComponent={
<ListItem
text={lnurlPayParams?.address || memo || lnurlPayParams?.domain || 'No description'}
text={lnurlPayParams?.address || memo || lnurlPayParams?.domain || translate("common.noDescPlaceholder")}
subText={lnurlDescription}
LeftComponent={
<Icon
@@ -634,8 +636,8 @@ const satsColor = colors.palette.primary200
mintBalances={availableMintBalances}
selectedMintBalance={mintBalanceToTransferFrom}
unit={unit}
title='Pay from'
confirmTitle='Pay now'
title={translate("payCommon.payFrom")}
confirmTitle={translate("payCommon.payNow")}
onMintBalanceSelect={onMintBalanceSelect}
onCancel={onClose}
onMintBalanceConfirm={transfer}
@@ -696,7 +698,7 @@ const satsColor = colors.palette.primary200
<ResultModalInfo
icon="faCheckCircle"
iconColor={colors.palette.success200}
title="Payment completed"
title={translate('payCommon.completed')}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
@@ -705,9 +707,9 @@ const satsColor = colors.palette.primary200
tx={'common.close'}
onPress={() => {
if(isInvoiceDonation) {
navigation.navigate('ContactsNavigator', {screen: 'Contacts', params: {}})
navigation.navigate('ContactsNavigator', {screen: 'Contacts', params: {}})
} else {
navigation.navigate('Wallet', {})
navigation.navigate('Wallet', {})
}
}}
/>
@@ -720,7 +722,7 @@ const satsColor = colors.palette.primary200
<ResultModalInfo
icon="faRotate"
iconColor={colors.palette.accent300}
title="Transfer reverted"
title={translate('transactionCommon.reverted')}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
@@ -738,7 +740,7 @@ const satsColor = colors.palette.primary200
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.angry500}
title={resultModalInfo?.title || 'Payment failed'}
title={resultModalInfo?.title || translate('payCommon.failed')}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
@@ -756,7 +758,7 @@ const satsColor = colors.palette.primary200
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.iconYellow300}
title="Payment is pending"
title={translate('payCommon.isPending')}
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
+7 -6
View File
@@ -26,6 +26,7 @@ import AppError from '../utils/AppError'
import { log } from '../services'
import {Env} from '../utils/envtypes'
import { CommonActions, StackActions } from '@react-navigation/native'
import { translate } from '../i18n'
const deploymentKey = APP_ENV === Env.PROD ? CODEPUSH_PRODUCTION_DEPLOYMENT_KEY : CODEPUSH_STAGING_DEPLOYMENT_KEY
@@ -90,11 +91,11 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
switch (status) {
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
log.trace('Downloading update...')
setInfo('Downloading update...')
setInfo(translate("updateScreen.downloading"))
break
case codePush.SyncStatus.INSTALLING_UPDATE:
log.trace('Installing update...')
setInfo('Installing update...')
setInfo(translate("updateScreen.installing"))
break
}
})
@@ -134,7 +135,7 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
return (
<Screen style={$screen}>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text preset="heading" text="Update manager" style={{color: 'white'}} />
<Text preset="heading" tx="updateScreen.updateManagerTitle" style={{color: 'white'}} />
</View>
<View style={$contentContainer}>
<Card
@@ -145,7 +146,7 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
<>
<ListItem
tx='updateScreen.updateAvailable'
subTx={'updateScreen.updateAvailableDesc'}
subTx='updateScreen.updateAvailableDesc'
leftIcon='faWandMagicSparkles'
leftIconColor={colors.palette.iconMagenta200}
leftIconInverse={true}
@@ -153,7 +154,7 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
/>
<ListItem
tx='updateScreen.updateNew'
subText={updateDescription || 'UX and performance improvements'}
subText={updateDescription || translate("updateScreen.defaultUpdateDesc")}
leftIcon='faInfoCircle'
leftIconColor={colors.palette.neutral500}
topSeparator={true}
@@ -207,7 +208,7 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
/>
<Button
onPress={gotoGithub}
text='APK on Github'
tx="updateScreen.apkOnGithub"
preset='secondary'
style={{marginTop: spacing.medium}}
/>
+4 -3
View File
@@ -6,6 +6,7 @@ import Clipboard from "@react-native-clipboard/clipboard"
import { moderateVerticalScale } from "@gocodingnow/rn-size-matters"
import { colors, spacing } from "../../theme"
import { useState } from "react"
import { translate } from "../../i18n"
export const QRCodeBlock = function (props: {
qrCodeData: string
@@ -29,11 +30,11 @@ export const QRCodeBlock = function (props: {
if (result.action === Share.sharedAction) {
setTimeout(
() => infoMessage('Lightning invoice has been shared, waiting to be paid by receiver.'),
() => infoMessage(translate('lightningInvoiceSharedWaiting')),
500,
)
} else if (result.action === Share.dismissedAction) {
infoMessage('Sharing cancelled')
infoMessage(translate('share.cancelled'))
}
} catch (e: any) {
setQrError(e)
@@ -56,7 +57,7 @@ export const QRCodeBlock = function (props: {
style={{backgroundColor: 'white', paddingBottom: 0}}
ContentComponent={qrError ? (
<ListItem
text='Could not display QR code, copy or share the Ecash token instead.'
text={translate("qr.fail")}
subText={qrError ? qrError.message : ''}
leftIcon='faTriangleExclamation'
containerStyle={{marginVertical: spacing.large}}
+17 -17
View File
@@ -306,7 +306,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
}
if (mintsStore.alreadyExists(newMintUrl)) {
const msg = translate('mintsScreen.mintExists')
const msg = translate('walletScreen.mintExists')
log.info(msg)
setInfo(msg)
return
@@ -530,7 +530,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
onPress={toggleSendModal}
style={[{backgroundColor: mainButtonColor, borderWidth: 1, borderColor: screenBg}, $buttonTopup]}
preset='tertiary'
text='Send'
tx='payCommon.send'
/>
<Button
RightAccessory={() => (
@@ -554,7 +554,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
/>
)}
onPress={toggleReceiveModal}
text='Receive'
tx='payCommon.receive'
style={[{backgroundColor: mainButtonColor, borderWidth: 1, borderColor: screenBg}, $buttonPay]}
preset='tertiary'
/>
@@ -576,8 +576,8 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
/>
</View>
}
text='New Minibits version is available'
subText='Updates provide new functionalities and important bug fixes. View details in the Update manager.'
tx="updateModal.title"
subTx="updateModal.desc"
onPress={gotoUpdate}
/>
}
@@ -591,14 +591,14 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
<>
<ListItem
leftIcon='faMoneyBill1'
text='Send Ecash'
subText='Share ecash or send it to one of your contacts'
tx="walletScreen.sendEcash"
subTx="walletScreen.sendEcashDesc"
onPress={gotoSend}
/>
<ListItem
leftIcon='faBolt'
text='Pay with Lightning'
subText='Pay invoice or to a Lightning address'
tx="walletScreen.payWithLightning"
subTx="walletScreen.payWithLightningDesc"
onPress={gotoLightningPay}
/>
</>
@@ -613,14 +613,14 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
<>
<ListItem
leftIcon='faMoneyBill1'
text='Receive Ecash'
subText='Paste or scan ecash token'
tx="walletScreen.receiveEcash"
subTx="walletScreen.receiveEcashDesc"
onPress={gotoTokenReceive}
/>
<ListItem
leftIcon='faBolt'
text='Topup with Lightning'
subText='Create Lightning invoice to topup your balance'
tx='walletScreen.topupWithLightning'
subTx="walletScreen.topupWithLightningDesc"
onPress={gotoTopup}
/>
</>
@@ -708,7 +708,7 @@ const PromoBlock = function (props: {addMint: any}) {
<Button
preset='default'
onPress={props.addMint}
text='Add your first mint'
tx="walletScreen.addFirstMint"
/>
</View>
}
@@ -819,7 +819,7 @@ const MintsByUnitListItem = observer(function (props: {
{selectedMintUrl === mint.mintUrl && (
<View style={{flexDirection: 'row', marginBottom: spacing.small, justifyContent: 'flex-start'}}>
<Button
text={'Topup'}
tx="walletScreen.topup"
LeftAccessory={() => (
<Icon
icon='faPlus'
@@ -851,7 +851,7 @@ const MintsByUnitListItem = observer(function (props: {
style={{minHeight: moderateVerticalScale(40), paddingVertical: moderateVerticalScale(spacing.tiny)}}
/>*/}
<Button
text={'Pay'}
tx="walletScreen.pay"
LeftAccessory={() => (
<Icon
icon='faBolt'
@@ -887,7 +887,7 @@ const MintsByUnitListItem = observer(function (props: {
}}
/>*/}
<Button
text={'Mint'}
tx="wallerScreen.mintButton"
LeftAccessory={() => (
<View style={{marginHorizontal: spacing.extraSmall}}>
<SvgXml
+3 -3
View File
@@ -216,12 +216,12 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
<Button
onPress={gotoWallet}
preset='secondary'
text='Got it, take me to the wallet'
tx="welcomeScreen.lastPageConfirmButton"
/>
<Button
onPress={gotoRecovery}
preset='tertiary'
text='Recover lost wallet'
tx="welcomeScreen.lastPageRecoverLostWalletButton"
LeftAccessory={() => {return<Icon icon='faRotate'/>}}
style={{marginTop: spacing.medium}}
/>
@@ -246,7 +246,7 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
</View>
</View>
{error && <ErrorModal error={error} />}
{isLoading && <Loading statusMessage={'Creating wallet seed, this takes a while...'} style={{backgroundColor: headerBg, opacity: 1}} textStyle={{color: 'white'}}/>}
{isLoading && <Loading statusMessage={translate("welcomeScreen.generatingWalletSeedStatusMessage")} style={{backgroundColor: headerBg, opacity: 1}} textStyle={{color: 'white'}}/>}
</Screen>
)
}
+3 -1
View File
@@ -5,6 +5,8 @@
"typeRoots": ["./src/utils/envtypes"],
"paths": {
"nostr-tools/*": ["./node_modules/nostr-tools/lib/types/*"]
}
},
"resolveJsonModule": true,
"esModuleInterop": true,
}
}