Compare commits

...
Author SHA1 Message Date
Forte11Cuba 76931f7b1d fix: parse URI fragment as key-value pairs instead of substring 2026-03-13 14:09:56 -06:00
Forte11Cuba 115843ce51 fix: guard against infinite loop on zero-data IsoDep response 2026-03-13 12:43:35 -06:00
Forte11Cuba 713f13a46d fix: address CodeRabbit review — await NFC sessions, log errors, validate tokens 2026-03-13 12:30:46 -06:00
Forte11Cuba 560d6fc683 feat: phone-to-phone NFC via HCE with proprietary AID 2026-03-13 12:05:36 -06:00
Forte11Cuba c0f8b166ea feat: NFC support for Cashu token read/write 2026-03-12 10:53:50 -06:00
Forte11andGitHub 6b68367c0d Merge pull request #64 from Forte11Cuba/feature/peanut-emoji-encoding
feat: add peanut emoji encoding for Cashu tokens
2026-03-12 08:33:05 -06:00
Forte11Cuba d80ed4caa7 fix: handle malformed peanut tokens with explicit error message 2026-03-12 08:20:52 -06:00
Forte11Cuba a627ed39f2 fix: guard clipboard callback with mounted check in ReceiveScreen 2026-03-12 08:06:24 -06:00
Forte11Cuba 5d00213d95 fix: peanut decode trimLeft consistency and auto-decode on receive 2026-03-12 07:30:52 -06:00
Forte11Cuba 2e3bb5dcc3 feat: add peanut emoji encoding for Cashu tokens 2026-03-12 06:54:50 -06:00
Forte11andGitHub 749d44650d Merge pull request #63 from Forte11Cuba/cleanup/css-duplicates-and-i18n
fix: auto version from pubspec, remove Cuba Bitcoin badge, LaChispa tappable link
2026-02-26 03:09:30 -06:00
Forte11Cuba 3ff2d6ef84 fix: defensive error handling for version load and LaChispa link 2026-02-26 03:04:58 -06:00
Forte11Cuba 18c72ebf1d fix: auto version from pubspec, remove Cuba Bitcoin badge, LaChispa tappable link 2026-02-26 02:50:40 -06:00
Forte11Cuba f9a70bf1ed ci: add universal APK and stable download URL
Release APK / build (push) Canceled after 0s
2026-02-25 05:27:34 -06:00
Forte11Cuba 22434245f2 bump version to v0.1.0 2026-02-25 04:48:05 -06:00
Forte11Cuba fb76bde5b7 fix: remove stale precompiled .so and ignore jniLibs 2026-02-25 04:46:44 -06:00
Forte11andGitHub 365047e73c Merge pull request #62 from Forte11Cuba/feature/p2pk-qr-scan
feat: add QR scan button for P2PK pubkey input (#59)
2026-02-23 16:31:48 -06:00
Forte11Cuba c374c042c7 fix: add user feedback for invalid QR and camera errors in P2PK scanner 2026-02-23 16:30:30 -06:00
Forte11Cuba 0f7e89a687 feat: add QR scan button for P2PK pubkey input (#59) 2026-02-23 16:21:29 -06:00
Forte11andGitHub 73582c4a69 Merge pull request #61 from Forte11Cuba/fix/cancel-send-on-failure
fix: release reserved proofs when confirmSend fails (#58)
2026-02-23 15:51:19 -06:00
Forte11Cuba 3e9fea34eb fix: guard cancelSend to avoid masking original send error 2026-02-23 15:48:46 -06:00
Forte11Cuba 348ef1d987 fix: release reserved proofs when confirmSend fails (#58) 2026-02-23 15:42:02 -06:00
Forte11andGitHub fc72bb33b2 Merge pull request #57 from Forte11Cuba/feature/enable-p2pk-send
feat: enable P2PK send with CDK 0.15 includeFee support
2026-02-23 14:29:52 -06:00
Forte11Cuba 7a9ce65e55 fix: guard against short pubkey in debug log and async paste disposal 2026-02-23 14:17:22 -06:00
Forte11Cuba 604dd65266 feat: enable P2PK send with CDK 0.15 includeFee support 2026-02-23 13:52:25 -06:00
Forte11andGitHub 42f59e877f Merge pull request #56 from Forte11Cuba/fix/pending-invoice-error-handling
fix: add try/catch to _loadPendingMintInvoice to prevent stuck loading state
2026-02-23 13:20:07 -06:00
Forte11Cuba 8b0e0dcd15 fix: log error in _loadPendingMintInvoice catch block 2026-02-23 13:18:28 -06:00
Forte11Cuba 0ac2a8b3ad fix: add try/catch to _loadPendingMintInvoice to prevent stuck loading state 2026-02-23 13:12:16 -06:00
Forte11andGitHub a97d7d45bc Merge pull request #55 from Forte11Cuba/fix/invoice-placeholder-flash
ui: suppress invoice placeholder flash while loading pending mint invoice
2026-02-21 01:38:33 -06:00
Forte11Cuba bf2c4771dd ui: suppress invoice placeholder flash while loading pending mint invoice 2026-02-21 01:30:53 -06:00
Forte11andGitHub 5821ea2be5 Merge pull request #54 from Forte11Cuba/ui/offline-send-select-all-label
ui: add label to select all checkbox in offline send screen
2026-02-21 01:25:01 -06:00
47 changed files with 2764 additions and 560 deletions
+8 -1
View File
@@ -41,15 +41,20 @@ jobs:
- name: Generate localizations
run: flutter gen-l10n
- name: Build release APKs
- name: Build release APKs (split per ABI)
run: flutter build apk --release --split-per-abi
- name: Build universal APK
run: flutter build apk --release
- name: Rename APKs
run: |
cd build/app/outputs/flutter-apk
mv app-arm64-v8a-release.apk elcaju-${{ github.ref_name }}-arm64.apk
mv app-armeabi-v7a-release.apk elcaju-${{ github.ref_name }}-armeabi-v7a.apk
mv app-x86_64-release.apk elcaju-${{ github.ref_name }}-x86_64.apk
mv app-release.apk elcaju-${{ github.ref_name }}-universal.apk
cp elcaju-${{ github.ref_name }}-universal.apk elcaju-universal.apk
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
@@ -58,4 +63,6 @@ jobs:
build/app/outputs/flutter-apk/elcaju-${{ github.ref_name }}-arm64.apk
build/app/outputs/flutter-apk/elcaju-${{ github.ref_name }}-armeabi-v7a.apk
build/app/outputs/flutter-apk/elcaju-${{ github.ref_name }}-x86_64.apk
build/app/outputs/flutter-apk/elcaju-${{ github.ref_name }}-universal.apk
build/app/outputs/flutter-apk/elcaju-universal.apk
generate_release_notes: true
+3
View File
@@ -44,3 +44,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
# Rust/CargoKit compiled native libraries
android/app/src/main/jniLibs/
+1 -1
View File
@@ -21,7 +21,7 @@ android {
defaultConfig {
applicationId "me.elcaju"
minSdk 21 // Required for FlutterSecureStorage (EncryptedSharedPreferences)
minSdkVersion flutter.minSdkVersion // Required for FlutterSecureStorage (EncryptedSharedPreferences)
targetSdk flutter.targetSdkVersion
versionCode flutter.versionCode
versionName flutter.versionName
+16
View File
@@ -7,6 +7,9 @@
<!-- Cámara opcional: la app funciona sin ella (pegando desde portapapeles) -->
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<!-- NFC para compartir tokens Cashu -->
<uses-permission android:name="android.permission.NFC"/>
<uses-feature android:name="android.hardware.nfc" android:required="false"/>
<application
android:label="ElCaju"
@@ -34,6 +37,19 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- HCE service: phone emulates NFC tag for phone-to-phone transfers -->
<service
android:name=".NfcHceService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE">
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
</intent-filter>
<meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="@xml/hce_nfc" />
</service>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
@@ -1,5 +1,60 @@
package me.elcaju
import android.content.ComponentName
import android.nfc.NfcAdapter
import android.nfc.cardemulation.CardEmulation
import android.util.Log
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity()
class MainActivity : FlutterActivity() {
private val CHANNEL = "me.elcaju/nfc_hce"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"setPayload" -> {
val payload = call.argument<ByteArray>("payload")
NfcHceService.ndefPayload = payload
// Force Android to route the NDEF AID to our service
// instead of manufacturer services (Xiaomi Mi Share, etc.)
try {
val adapter = NfcAdapter.getDefaultAdapter(this)
if (adapter != null) {
val cardEmulation = CardEmulation.getInstance(adapter)
cardEmulation.setPreferredService(
this,
ComponentName(this, NfcHceService::class.java)
)
}
} catch (e: Exception) {
Log.w("MainActivity", "setPreferredService failed (non-critical)", e)
}
result.success(true)
}
"clearPayload" -> {
NfcHceService.ndefPayload = null
// Release preferred service routing
try {
val adapter = NfcAdapter.getDefaultAdapter(this)
if (adapter != null) {
val cardEmulation = CardEmulation.getInstance(adapter)
cardEmulation.unsetPreferredService(this)
}
} catch (e: Exception) {
Log.w("MainActivity", "unsetPreferredService failed (non-critical)", e)
}
result.success(true)
}
else -> result.notImplemented()
}
}
}
}
@@ -0,0 +1,140 @@
package me.elcaju
import android.nfc.cardemulation.HostApduService
import android.os.Bundle
import android.util.Log
/**
* Host Card Emulation service that makes the phone act as an NFC Forum Type 4 tag.
* Another phone in reader mode can tap and read the NDEF message we serve.
*
* The payload is set from Flutter via MainActivity's MethodChannel.
*/
class NfcHceService : HostApduService() {
companion object {
private const val TAG = "NfcHceService"
// Shared payload - set by Flutter via MethodChannel
@Volatile
var ndefPayload: ByteArray? = null
private val OK = byteArrayOf(0x90.toByte(), 0x00.toByte())
private val NOT_FOUND = byteArrayOf(0x6A.toByte(), 0x82.toByte())
// Capability Container (CC) file - fixed structure
// MLe=0xFF (255), MLc=0xFF (255), max NDEF=0x70FF (28671)
// Compatible with Numo
private val CC_FILE = byteArrayOf(
0x00, 0x0F, // CC length (15)
0x20, // Mapping version 2.0
0x00, 0xFF.toByte(), // MLe: max read 255 bytes
0x00, 0xFF.toByte(), // MLc: max write 255 bytes
0x04, 0x06, // NDEF File Control TLV
0xE1.toByte(), 0x04, // NDEF file ID
0x70, 0xFF.toByte(), // Max NDEF size: 28671 bytes
0x00, // Read access: open
0xFF.toByte() // Write access: denied
)
}
private var selectedFile: String = "none"
private var ndefFileCache: ByteArray? = null
override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray {
if (commandApdu.size < 4) return NOT_FOUND
val ins = commandApdu[1]
// SELECT command
if (ins == 0xA4.toByte()) {
return handleSelect(commandApdu)
}
// READ BINARY command
if (ins == 0xB0.toByte()) {
return handleRead(commandApdu)
}
return NOT_FOUND
}
private fun handleSelect(apdu: ByteArray): ByteArray {
// Select ElCaju proprietary AID (F04543414A5500)
// Avoids conflicts with Xiaomi Mi Share / Samsung Beam
if (apdu.size >= 12 && apdu[5] == 0xF0.toByte() && apdu[6] == 0x45.toByte()) {
selectedFile = "app"
Log.d(TAG, "Selected ElCaju Application (proprietary AID)")
return OK
}
// Select NDEF Application (AID: D2760000850101)
if (apdu.size >= 12 && apdu[5] == 0xD2.toByte() && apdu[6] == 0x76.toByte()) {
selectedFile = "app"
Log.d(TAG, "Selected NDEF Application")
return OK
}
// Select by file ID
if (apdu.size >= 7) {
val fileId = ((apdu[5].toInt() and 0xFF) shl 8) or (apdu[6].toInt() and 0xFF)
when (fileId) {
0xE103 -> {
selectedFile = "cc"
Log.d(TAG, "Selected CC file")
return OK
}
0xE104 -> {
selectedFile = "ndef"
// Cache the NDEF file when selected
val payload = ndefPayload
if (payload != null) {
val file = ByteArray(2 + payload.size)
file[0] = (payload.size shr 8).toByte()
file[1] = (payload.size and 0xFF).toByte()
System.arraycopy(payload, 0, file, 2, payload.size)
ndefFileCache = file
Log.d(TAG, "Selected NDEF file (${payload.size} bytes payload)")
} else {
ndefFileCache = null
Log.w(TAG, "Selected NDEF file but no payload set")
}
return OK
}
}
}
return NOT_FOUND
}
private fun handleRead(apdu: ByteArray): ByteArray {
if (apdu.size < 5) return NOT_FOUND
val offset = ((apdu[2].toInt() and 0xFF) shl 8) or (apdu[3].toInt() and 0xFF)
// Le=0x00 means 256 bytes in short APDU encoding
var length = apdu[4].toInt() and 0xFF
if (length == 0) length = 256
val data = when (selectedFile) {
"cc" -> CC_FILE
"ndef" -> ndefFileCache ?: return NOT_FOUND
else -> return NOT_FOUND
}
if (offset >= data.size) {
Log.w(TAG, "Read offset $offset beyond data size ${data.size}")
return NOT_FOUND
}
val end = minOf(offset + length, data.size)
val response = data.copyOfRange(offset, end)
Log.d(TAG, "Read $selectedFile: offset=$offset len=$length returned=${response.size} bytes")
return response + OK
}
override fun onDeactivated(reason: Int) {
Log.d(TAG, "Deactivated (reason=$reason)")
selectedFile = "none"
ndefFileCache = null
}
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hce_description">ElCaju NFC</string>
</resources>
+12
View File
@@ -0,0 +1,12 @@
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/hce_description"
android:requireDeviceUnlock="false">
<aid-group
android:category="other"
android:description="@string/hce_description">
<!-- ElCaju proprietary AID (avoids Xiaomi/Samsung NFC service conflicts) -->
<aid-filter android:name="F04543414A5500" />
<!-- NDEF Tag Application AID (NFC Forum Type 4) -->
<aid-filter android:name="D2760000850101" />
</aid-group>
</host-apdu-service>
-1
View File
@@ -2,4 +2,3 @@ arb-dir: lib/l10n
template-arb-file: app_es.arb
output-localization-file: app_localizations.dart
output-class: L10n
synthetic-package: false
+483
View File
@@ -0,0 +1,483 @@
import 'dart:io' show Platform;
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:nfc_manager/nfc_manager.dart';
import 'package:nfc_manager/nfc_manager_android.dart';
import 'package:ndef_record/ndef_record.dart';
/// NFC availability state for UI rendering.
enum NfcState {
/// Device does not have NFC hardware.
unsupported,
/// Device has NFC but it is disabled in settings.
disabled,
/// NFC is supported and enabled.
enabled,
}
/// Service for reading and writing Cashu tokens via NFC.
///
/// Writes tokens as NDEF Text Records for maximum compatibility
/// with cashu.me and Numo. Reads both Text and URI records.
class NfcService {
static const _hceChannel = MethodChannel('me.elcaju/nfc_hce');
// ─── HCE (phone-to-phone) ───
/// Start emulating an NFC tag with the given text payload.
/// The phone will appear as an NFC tag to other devices.
static Future<void> startEmulating(String text) async {
final ndefMessage = _buildNdefTextMessage(text);
await _hceChannel.invokeMethod('setPayload', {'payload': ndefMessage});
}
/// Stop emulating an NFC tag.
static Future<void> stopEmulating() async {
await _hceChannel.invokeMethod('clearPayload');
}
/// Build an NDEF message with a Text Record.
/// Uses Short Record (SR) for payloads ≤ 255 bytes,
/// Long Record for > 255 bytes (compatible with Numo).
static Uint8List _buildNdefTextMessage(String text) {
final textBytes = Uint8List.fromList(text.codeUnits);
final languageCode = Uint8List.fromList('en'.codeUnits);
// NDEF Text Record payload: [status byte][lang code][text]
final recordPayload = Uint8List(1 + languageCode.length + textBytes.length);
recordPayload[0] = languageCode.length;
recordPayload.setRange(1, 1 + languageCode.length, languageCode);
recordPayload.setRange(1 + languageCode.length, recordPayload.length, textBytes);
final payloadLength = recordPayload.length;
final isShortRecord = payloadLength <= 255;
if (isShortRecord) {
// Short Record: flags(1) + typeLen(1) + payloadLen(1) + type(1) + payload
final record = Uint8List(4 + payloadLength);
record[0] = 0xD1; // MB|ME|SR|TNF=well-known
record[1] = 1; // type length
record[2] = payloadLength;
record[3] = 0x54; // 'T'
record.setRange(4, 4 + payloadLength, recordPayload);
return record;
} else {
// Long Record: flags(1) + typeLen(1) + payloadLen(4) + type(1) + payload
final record = Uint8List(7 + payloadLength);
record[0] = 0xC1; // MB|ME|TNF=well-known (no SR flag)
record[1] = 1; // type length
record[2] = (payloadLength >> 24) & 0xFF;
record[3] = (payloadLength >> 16) & 0xFF;
record[4] = (payloadLength >> 8) & 0xFF;
record[5] = payloadLength & 0xFF;
record[6] = 0x54; // 'T'
record.setRange(7, 7 + payloadLength, recordPayload);
return record;
}
}
// ─── Tag read/write ───
/// Check NFC state on the device.
static Future<NfcState> checkState() async {
if (!Platform.isAndroid) return NfcState.unsupported;
try {
final availability = await NfcManager.instance.checkAvailability();
return switch (availability) {
NfcAvailability.enabled => NfcState.enabled,
NfcAvailability.disabled => NfcState.disabled,
NfcAvailability.unsupported => NfcState.unsupported,
};
} catch (_) {
return NfcState.unsupported;
}
}
/// Write a Cashu token to an NFC tag as NDEF Text Record.
static Future<void> startWrite({
required String token,
required void Function() onSuccess,
required void Function(String error) onError,
}) async {
try {
await NfcManager.instance.startSession(
pollingOptions: {NfcPollingOption.iso14443},
onDiscovered: (NfcTag tag) async {
final ndef = NdefAndroid.from(tag);
if (ndef == null || !ndef.isWritable) {
onError('Tag is not writable');
await NfcManager.instance.stopSession();
return;
}
// Build NDEF Text Record: [status byte][language code][text]
final textBytes = Uint8List.fromList(token.codeUnits);
final languageCode = Uint8List.fromList('en'.codeUnits);
final payload = Uint8List(1 + languageCode.length + textBytes.length);
payload[0] = languageCode.length; // status byte (UTF-8, no length)
payload.setRange(1, 1 + languageCode.length, languageCode);
payload.setRange(1 + languageCode.length, payload.length, textBytes);
// Check size
if (payload.length + 7 > ndef.maxSize) {
onError('Token too large for this NFC tag '
'(${payload.length + 7}B > ${ndef.maxSize}B)');
await NfcManager.instance.stopSession();
return;
}
try {
final message = NdefMessage(records: [
NdefRecord(
typeNameFormat: TypeNameFormat.wellKnown,
type: Uint8List.fromList([0x54]), // 'T' = Text Record
identifier: Uint8List(0),
payload: payload,
),
]);
await ndef.writeNdefMessage(message);
onSuccess();
await NfcManager.instance.stopSession();
} catch (e) {
onError(e.toString());
await NfcManager.instance.stopSession();
}
},
);
} catch (e) {
onError(e.toString());
}
}
/// Stop any active NFC session.
static Future<void> stopWrite() async {
await NfcManager.instance.stopSession();
}
/// Start reading NFC tags for Cashu tokens.
/// Tries IsoDep APDU first (phone-to-phone HCE, bypasses manufacturer
/// NFC services), falls back to NDEF (physical tags).
static Future<void> startRead({
required void Function(String token) onTokenRead,
required void Function(String error) onError,
}) async {
try {
await NfcManager.instance.startSession(
pollingOptions: {NfcPollingOption.iso14443},
onDiscovered: (NfcTag tag) async {
try {
final diagnostics = <String>[];
// 1. Try IsoDep first (HCE phone-to-phone)
final isoDep = IsoDepAndroid.from(tag);
if (isoDep != null) {
final (token, isoInfo) = await _readViaIsoDep(isoDep);
if (token != null) {
onTokenRead(token);
await NfcManager.instance.stopSession();
return;
}
diagnostics.add('IsoDep: $isoInfo');
} else {
diagnostics.add('IsoDep: not available');
}
// 2. Fallback: NDEF (physical tags)
final ndef = NdefAndroid.from(tag);
if (ndef != null) {
final message = ndef.cachedNdefMessage ?? await ndef.getNdefMessage();
if (message != null) {
final token = _extractToken(message);
if (token != null) {
onTokenRead(token);
await NfcManager.instance.stopSession();
return;
}
diagnostics.add('NDEF: ${message.records.length} records, no Cashu token');
} else {
diagnostics.add('NDEF: no message');
}
} else {
diagnostics.add('NDEF: not available');
}
onError('No Cashu token found [${diagnostics.join('; ')}]');
await NfcManager.instance.stopSession();
} catch (e) {
onError(e.toString());
await NfcManager.instance.stopSession();
}
},
);
} catch (e) {
onError(e.toString());
}
}
/// Read NDEF from HCE via IsoDep APDU commands (like Numo).
/// Bypasses manufacturer NFC services (Xiaomi, Samsung, etc).
/// Returns (token, diagnosticInfo) tuple.
static Future<(String?, String)> _readViaIsoDep(IsoDepAndroid isoDep) async {
String _hex(Uint8List bytes) =>
bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
try {
// Set longer timeout (5s) to avoid TagLostException
await isoDep.setTimeout(5000);
// Try ElCaju proprietary AID first (avoids Xiaomi/Samsung conflicts)
final selectElCaju = await isoDep.transceive(Uint8List.fromList([
0x00, 0xA4, 0x04, 0x00, 0x07,
0xF0, 0x45, 0x43, 0x41, 0x4A, 0x55, 0x00,
0x00,
]));
final bool elCajuSelected = _isOk(selectElCaju);
// Fallback: standard NDEF Application AID
if (!elCajuSelected) {
final selectApp = await isoDep.transceive(Uint8List.fromList([
0x00, 0xA4, 0x04, 0x00, 0x07,
0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01,
0x00,
]));
if (!_isOk(selectApp)) {
return (null, 'SELECT APP failed (both AIDs): elcaju=${_hex(selectElCaju)}, ndef=${_hex(selectApp)}');
}
}
// SELECT NDEF file (E104)
final selectNdef = await isoDep.transceive(Uint8List.fromList([
0x00, 0xA4, 0x00, 0x0C, 0x02,
0xE1, 0x04,
]));
if (!_isOk(selectNdef)) {
return (null, 'SELECT NDEF failed: ${_hex(selectNdef)}');
}
// READ NLEN (first 2 bytes)
final nlenResponse = await isoDep.transceive(Uint8List.fromList([
0x00, 0xB0, 0x00, 0x00, 0x02,
]));
if (nlenResponse.length < 4 || !_isOk(nlenResponse)) {
return (null, 'READ NLEN failed: ${_hex(nlenResponse)}');
}
final ndefLen = (nlenResponse[0] << 8) | nlenResponse[1];
if (ndefLen == 0) {
return (null, 'NLEN=0 (no payload set on emitter)');
}
// READ NDEF message in chunks (max 255 bytes per read)
final ndefBytes = BytesBuilder();
var offset = 2; // skip NLEN
var remaining = ndefLen;
while (remaining > 0) {
final chunkSize = remaining > 255 ? 255 : remaining;
final readCmd = Uint8List.fromList([
0x00, 0xB0,
(offset >> 8) & 0xFF,
offset & 0xFF,
chunkSize,
]);
final chunk = await isoDep.transceive(readCmd);
if (chunk.length < 2 || !_isOk(chunk)) {
return (null, 'READ chunk at offset=$offset failed: ${_hex(chunk)}');
}
// Response = data + SW1 SW2 (last 2 bytes are status)
final bytesRead = chunk.length - 2;
if (bytesRead == 0) {
return (null, 'READ chunk at offset=$offset returned no data');
}
ndefBytes.add(chunk.sublist(0, bytesRead));
offset += bytesRead;
remaining -= bytesRead;
}
// Parse NDEF message from raw bytes
final raw = ndefBytes.toBytes();
if (raw.isEmpty) {
return (null, 'read ${ndefLen}B but empty after parsing');
}
final token = _parseNdefAndExtractToken(Uint8List.fromList(raw));
if (token != null) {
return (token, 'OK');
}
return (null, 'NDEF parsed (${raw.length}B) but no Cashu token, hex: ${_hex(Uint8List.fromList(raw.length > 40 ? raw.sublist(0, 40) : raw))}...');
} catch (e) {
final msg = e.toString();
if (msg.contains('TagLostException')) {
return (null, 'connection lost (hold phones together longer)');
}
return (null, 'error: $msg');
}
}
/// Check if APDU response ends with 90 00 (OK).
static bool _isOk(Uint8List response) {
if (response.length < 2) return false;
return response[response.length - 2] == 0x90 &&
response[response.length - 1] == 0x00;
}
/// Parse raw NDEF bytes and extract Cashu token.
static String? _parseNdefAndExtractToken(Uint8List raw) {
if (raw.isEmpty) return null;
var pos = 0;
while (pos < raw.length) {
if (pos + 3 > raw.length) break;
final flags = raw[pos];
final tnf = flags & 0x07;
final isShortRecord = (flags & 0x10) != 0;
final hasIdLength = (flags & 0x08) != 0;
pos++;
final typeLength = raw[pos]; pos++;
int payloadLength;
if (isShortRecord) {
payloadLength = raw[pos]; pos++;
} else {
if (pos + 4 > raw.length) break;
payloadLength = (raw[pos] << 24) | (raw[pos+1] << 16) |
(raw[pos+2] << 8) | raw[pos+3];
pos += 4;
}
int idLength = 0;
if (hasIdLength) {
idLength = raw[pos]; pos++;
}
// Type
if (pos + typeLength > raw.length) break;
final type = raw.sublist(pos, pos + typeLength); pos += typeLength;
// ID (skip)
pos += idLength;
// Payload
if (pos + payloadLength > raw.length) break;
final payload = raw.sublist(pos, pos + payloadLength);
pos += payloadLength;
// Check: Text Record (TNF=0x01, type='T')
if (tnf == 0x01 && typeLength == 1 && type[0] == 0x54) {
if (payload.isNotEmpty) {
final langLen = payload[0] & 0x3F;
if (payload.length > 1 + langLen) {
final text = String.fromCharCodes(payload.sublist(1 + langLen));
if (_isCashuToken(text)) return text;
}
}
}
// Check: URI Record (TNF=0x01, type='U')
if (tnf == 0x01 && typeLength == 1 && type[0] == 0x55) {
if (payload.isNotEmpty) {
const prefixes = ['', 'http://www.', 'https://www.', 'http://', 'https://'];
final prefixIdx = payload[0];
final prefix = prefixIdx < prefixes.length ? prefixes[prefixIdx] : '';
final rest = String.fromCharCodes(payload.sublist(1));
final uri = '$prefix$rest';
final token = _extractTokenFromUri(uri);
if (token != null) return token;
}
}
// If ME (Message End) flag set, stop
if ((flags & 0x40) != 0) break;
}
return null;
}
/// Stop any active NFC read session.
static Future<void> stopRead() async {
await NfcManager.instance.stopSession();
}
/// Extract a Cashu token from an NDEF message.
static String? _extractToken(NdefMessage message) {
for (final record in message.records) {
// Text Record
if (record.typeNameFormat == TypeNameFormat.wellKnown &&
record.type.length == 1 &&
record.type[0] == 0x54) {
final text = _decodeTextRecord(record);
if (text != null && _isCashuToken(text)) {
return text;
}
}
// URI Record
if (record.typeNameFormat == TypeNameFormat.wellKnown &&
record.type.length == 1 &&
record.type[0] == 0x55) {
final uri = _decodeUriRecord(record);
if (uri != null) {
final token = _extractTokenFromUri(uri);
if (token != null) return token;
}
}
}
return null;
}
/// Decode NDEF Text Record payload.
static String? _decodeTextRecord(NdefRecord record) {
if (record.payload.isEmpty) return null;
final languageCodeLength = record.payload[0] & 0x3F;
if (record.payload.length <= 1 + languageCodeLength) return null;
return String.fromCharCodes(
record.payload.sublist(1 + languageCodeLength));
}
/// Decode NDEF URI Record payload.
static String? _decodeUriRecord(NdefRecord record) {
if (record.payload.isEmpty) return null;
const prefixes = [
'', // 0x00
'http://www.', // 0x01
'https://www.', // 0x02
'http://', // 0x03
'https://', // 0x04
];
final prefixIndex = record.payload[0];
final prefix = prefixIndex < prefixes.length ? prefixes[prefixIndex] : '';
final rest = String.fromCharCodes(record.payload.sublist(1));
return '$prefix$rest';
}
/// Extract token from a URL like https://wallet.com/#token=cashuA...
static String? _extractTokenFromUri(String uri) {
try {
final parsed = Uri.parse(uri);
final fragmentParams = Uri.splitQueryString(parsed.fragment);
final fragmentToken = fragmentParams['token'];
if (fragmentToken != null && _isCashuToken(fragmentToken)) {
return fragmentToken;
}
final tokenParam = parsed.queryParameters['token'];
if (tokenParam != null && _isCashuToken(tokenParam)) {
return tokenParam;
}
} catch (_) {}
return null;
}
/// Check if a string looks like a Cashu token.
static bool _isCashuToken(String text) {
final lower = text.toLowerCase().trim();
return lower.startsWith('cashua') ||
lower.startsWith('cashub') ||
lower.startsWith('creqa');
}
}
+12 -1
View File
@@ -1,5 +1,7 @@
// Parser para detectar el tipo de dato entrante (QR, clipboard, etc.)
// Soporta: tokens Cashu (A/B), invoices Lightning, URLs de mint, payment requests
// Soporta: tokens Cashu (A/B), invoices Lightning, URLs de mint, payment requests, peanut emoji
import 'peanut_codec.dart';
/// Modo de escaneo
enum ScanMode {
@@ -57,6 +59,15 @@ class IncomingDataParser {
/// Detecta el tipo de dato y extrae información relevante
static ParsedData parse(String data) {
final trimmed = data.trim();
// Peanut-encoded token (🥜 + variation selectors)
if (PeanutCodec.isPeanut(trimmed)) {
final decoded = PeanutCodec.decode(trimmed);
if (decoded != null) {
return parse(decoded);
}
}
final lower = trimmed.toLowerCase();
// Token Cashu (cashuA... o cashuB...)
+62
View File
@@ -0,0 +1,62 @@
/// Peanut encoding/decoding for Cashu tokens.
///
/// Encodes Cashu tokens into invisible Unicode Variation Selectors
/// appended to a 🥜 emoji, compatible with cashu.me's implementation.
///
/// Each character of the base64 token is mapped to a Variation Selector:
/// - charCode 0-15 → VS1-VS16 (U+FE00 to U+FE0F)
/// - charCode 16-255 → VS17-VS256 (U+E0100 to U+E01EF)
class PeanutCodec {
static const String _peanutEmoji = '🥜';
/// Encodes a Cashu token string into peanut format (🥜 + variation selectors).
static String encode(String token) {
final buffer = StringBuffer(_peanutEmoji);
for (int i = 0; i < token.length; i++) {
final byte = token.codeUnitAt(i);
if (byte < 16) {
// VS1-VS16: U+FE00 to U+FE0F
buffer.writeCharCode(0xFE00 + byte);
} else if (byte < 256) {
// VS17-VS256: U+E0100 to U+E01EF
buffer.writeCharCode(0xE0100 + (byte - 16));
}
}
return buffer.toString();
}
/// Decodes a peanut-encoded string back to the original Cashu token.
/// Returns null if the input is not valid peanut format.
static String? decode(String peanut) {
final trimmed = peanut.trimLeft();
if (!trimmed.startsWith(_peanutEmoji)) return null;
final buffer = StringBuffer();
final runes = trimmed.runes.toList();
// Skip the first rune (🥜 emoji)
for (int i = 1; i < runes.length; i++) {
final codePoint = runes[i];
if (codePoint >= 0xFE00 && codePoint <= 0xFE0F) {
// VS1-VS16 → byte 0-15
buffer.writeCharCode(codePoint - 0xFE00);
} else if (codePoint >= 0xE0100 && codePoint <= 0xE01EF) {
// VS17-VS256 → byte 16-255
buffer.writeCharCode(codePoint - 0xE0100 + 16);
}
// Skip any other characters (whitespace, etc.)
}
final result = buffer.toString();
return result.isNotEmpty ? result : null;
}
/// Returns true if the string starts with the 🥜 emoji.
static bool isPeanut(String data) {
return data.trimLeft().startsWith(_peanutEmoji);
}
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "Bewahre diesen Token auf, bis der Empfänger ihn einlöst. Wenn du ihn verlierst, verlierst du die Mittel.",
"tokenCopiedToClipboard": "Token in Zwischenablage kopiert",
"copyAsEmoji": "Als Emoji kopieren",
"emojiCopiedToClipboard": "Token als Emoji kopiert 🥜",
"peanutDecodeError": "Emoji-Token konnte nicht dekodiert werden. Möglicherweise beschädigt.",
"nfcWrite": "Auf NFC-Tag schreiben",
"nfcRead": "NFC-Tag lesen",
"nfcHoldNear": "Gerät an NFC-Tag halten...",
"nfcWriteSuccess": "Token auf NFC-Tag geschrieben",
"nfcWriteError": "NFC-Schreibfehler: {error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "NFC-Lesefehler: {error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFC ist deaktiviert. Aktiviere es in den Einstellungen.",
"nfcUnsupported": "Dieses Gerät unterstützt kein NFC",
"amountToDeposit": "Einzuzahlender Betrag:",
"descriptionOptional": "Beschreibung (optional):",
@@ -296,7 +310,7 @@
}
},
"close": "Schließen",
"aboutDescription": "Eine Cashu Wallet mit kubanischer DNA für die ganze Welt. Bruder von La Chispa.",
"aboutDescription": "Eine Cashu Wallet mit kubanischer DNA für die ganze Welt. Bruder von LaChispa.",
"couldNotOpenLink": "Link konnte nicht geöffnet werden",
"deleteWalletQuestion": "Wallet löschen?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "Ungültiger nsec",
"p2pkErrorKeyAlreadyExists": "Dieser Schlüssel existiert bereits",
"p2pkErrorKeyNotFound": "Schlüssel nicht gefunden",
"p2pkErrorCannotDeletePrimary": "Primärschlüssel kann nicht gelöscht werden",
"p2pkSendComingSoon": "Demnächst verfügbar"
"p2pkErrorCannotDeletePrimary": "Primärschlüssel kann nicht gelöscht werden"
}
+24 -3
View File
@@ -112,6 +112,28 @@
"tokenCashuAnimatedQr": "Cashu Token (animated QR - {fragments} UR fragments)",
"keepTokenWarning": "Keep this token until the recipient claims it. If you lose it, you will lose the funds.",
"tokenCopiedToClipboard": "Token copied to clipboard",
"copyAsEmoji": "Copy as emoji",
"emojiCopiedToClipboard": "Token copied as emoji 🥜",
"peanutDecodeError": "Could not decode emoji token. It may be corrupted.",
"nfcWrite": "Write to NFC tag",
"nfcRead": "Read NFC tag",
"nfcHoldNear": "Hold device near NFC tag...",
"nfcWriteSuccess": "Token written to NFC tag",
"nfcWriteError": "NFC write error: {error}",
"@nfcWriteError": {
"placeholders": {
"error": { "type": "String" }
}
},
"nfcReadError": "NFC read error: {error}",
"@nfcReadError": {
"placeholders": {
"error": { "type": "String" }
}
},
"nfcDisabled": "NFC is disabled. Enable it in Settings.",
"nfcUnsupported": "This device does not support NFC",
"amountToDeposit": "Amount to deposit:",
"descriptionOptional": "Description (optional):",
@@ -234,7 +256,7 @@
"selectLanguage": "Select language",
"languageChanged": "Language changed to {language}",
"close": "Close",
"aboutDescription": "A Cashu wallet with Cuban DNA for the entire world. Brother of La Chispa.",
"aboutDescription": "A Cashu wallet with Cuban DNA for the entire world. Brother of LaChispa.",
"couldNotOpenLink": "Could not open link",
"deleteWalletQuestion": "Delete wallet?",
@@ -401,6 +423,5 @@
"p2pkErrorInvalidNsec": "Invalid nsec",
"p2pkErrorKeyAlreadyExists": "This key already exists",
"p2pkErrorKeyNotFound": "Key not found",
"p2pkErrorCannotDeletePrimary": "Cannot delete primary key",
"p2pkSendComingSoon": "Coming soon"
"p2pkErrorCannotDeletePrimary": "Cannot delete primary key"
}
+24 -3
View File
@@ -137,6 +137,28 @@
},
"keepTokenWarning": "Guarda este token hasta que el receptor lo reclame. Si lo pierdes, perderás los fondos.",
"tokenCopiedToClipboard": "Token copiado al portapapeles",
"copyAsEmoji": "Copiar como emoji",
"emojiCopiedToClipboard": "Token copiado como emoji 🥜",
"peanutDecodeError": "No se pudo decodificar el token emoji. Puede estar corrupto.",
"nfcWrite": "Escribir en tag NFC",
"nfcRead": "Leer tag NFC",
"nfcHoldNear": "Acerca el dispositivo al tag NFC...",
"nfcWriteSuccess": "Token escrito en tag NFC",
"nfcWriteError": "Error NFC al escribir: {error}",
"@nfcWriteError": {
"placeholders": {
"error": { "type": "String" }
}
},
"nfcReadError": "Error NFC al leer: {error}",
"@nfcReadError": {
"placeholders": {
"error": { "type": "String" }
}
},
"nfcDisabled": "NFC está desactivado. Actívalo en Ajustes.",
"nfcUnsupported": "Este dispositivo no soporta NFC",
"amountToDeposit": "Monto a depositar:",
"descriptionOptional": "Descripción (opcional):",
@@ -296,7 +318,7 @@
}
},
"close": "Cerrar",
"aboutDescription": "Un wallet de Cashu con ADN cubano para el mundo entero. Hermano de La Chispa.",
"aboutDescription": "Un wallet de Cashu con ADN cubano para el mundo entero. Hermano de LaChispa.",
"couldNotOpenLink": "No se pudo abrir el enlace",
"deleteWalletQuestion": "¿Borrar wallet?",
@@ -521,6 +543,5 @@
"p2pkErrorInvalidNsec": "nsec inválido",
"p2pkErrorKeyAlreadyExists": "Esta clave ya existe",
"p2pkErrorKeyNotFound": "Clave no encontrada",
"p2pkErrorCannotDeletePrimary": "No se puede eliminar la clave principal",
"p2pkSendComingSoon": "Disponible próximamente"
"p2pkErrorCannotDeletePrimary": "No se puede eliminar la clave principal"
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "Conservez ce token jusqu'à ce que le destinataire le réclame. Si vous le perdez, vous perdrez les fonds.",
"tokenCopiedToClipboard": "Token copié dans le presse-papiers",
"copyAsEmoji": "Copier en emoji",
"emojiCopiedToClipboard": "Token copié en emoji 🥜",
"peanutDecodeError": "Impossible de décoder le token emoji. Il est peut-être corrompu.",
"nfcWrite": "Écrire sur tag NFC",
"nfcRead": "Lire tag NFC",
"nfcHoldNear": "Approchez l'appareil du tag NFC...",
"nfcWriteSuccess": "Token écrit sur le tag NFC",
"nfcWriteError": "Erreur NFC écriture : {error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "Erreur NFC lecture : {error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFC est désactivé. Activez-le dans les Paramètres.",
"nfcUnsupported": "Cet appareil ne prend pas en charge le NFC",
"amountToDeposit": "Montant à déposer :",
"descriptionOptional": "Description (optionnelle) :",
@@ -296,7 +310,7 @@
}
},
"close": "Fermer",
"aboutDescription": "Un portefeuille Cashu avec ADN cubain pour le monde entier. Frère de La Chispa.",
"aboutDescription": "Un portefeuille Cashu avec ADN cubain pour le monde entier. Frère de LaChispa.",
"couldNotOpenLink": "Impossible d'ouvrir le lien",
"deleteWalletQuestion": "Supprimer le portefeuille ?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "nsec invalide",
"p2pkErrorKeyAlreadyExists": "Cette clé existe déjà",
"p2pkErrorKeyNotFound": "Clé non trouvée",
"p2pkErrorCannotDeletePrimary": "Impossible de supprimer la clé principale",
"p2pkSendComingSoon": "Bientôt disponible"
"p2pkErrorCannotDeletePrimary": "Impossible de supprimer la clé principale"
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "Conserva questo token finché il destinatario non lo riscatta. Se lo perdi, perderai i fondi.",
"tokenCopiedToClipboard": "Token copiato negli appunti",
"copyAsEmoji": "Copia come emoji",
"emojiCopiedToClipboard": "Token copiato come emoji 🥜",
"peanutDecodeError": "Impossibile decodificare il token emoji. Potrebbe essere corrotto.",
"nfcWrite": "Scrivi su tag NFC",
"nfcRead": "Leggi tag NFC",
"nfcHoldNear": "Avvicina il dispositivo al tag NFC...",
"nfcWriteSuccess": "Token scritto sul tag NFC",
"nfcWriteError": "Errore NFC scrittura: {error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "Errore NFC lettura: {error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFC è disattivato. Attivalo nelle Impostazioni.",
"nfcUnsupported": "Questo dispositivo non supporta NFC",
"amountToDeposit": "Importo da depositare:",
"descriptionOptional": "Descrizione (opzionale):",
@@ -296,7 +310,7 @@
}
},
"close": "Chiudi",
"aboutDescription": "Un portafoglio Cashu con DNA cubano per il mondo intero. Fratello di La Chispa.",
"aboutDescription": "Un portafoglio Cashu con DNA cubano per il mondo intero. Fratello di LaChispa.",
"couldNotOpenLink": "Impossibile aprire il link",
"deleteWalletQuestion": "Eliminare portafoglio?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "nsec non valido",
"p2pkErrorKeyAlreadyExists": "Questa chiave esiste già",
"p2pkErrorKeyNotFound": "Chiave non trovata",
"p2pkErrorCannotDeletePrimary": "Impossibile eliminare la chiave principale",
"p2pkSendComingSoon": "Disponibile prossimamente"
"p2pkErrorCannotDeletePrimary": "Impossibile eliminare la chiave principale"
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "受取人が請求するまでこのトークンを保管してください。紛失すると資金を失います。",
"tokenCopiedToClipboard": "トークンをクリップボードにコピーしました",
"copyAsEmoji": "絵文字としてコピー",
"emojiCopiedToClipboard": "トークンを絵文字としてコピーしました 🥜",
"peanutDecodeError": "絵文字トークンをデコードできませんでした。破損している可能性があります。",
"nfcWrite": "NFCタグに書き込む",
"nfcRead": "NFCタグを読み取る",
"nfcHoldNear": "デバイスをNFCタグに近づけてください...",
"nfcWriteSuccess": "トークンをNFCタグに書き込みました",
"nfcWriteError": "NFC書き込みエラー: {error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "NFC読み取りエラー: {error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFCが無効です。設定で有効にしてください。",
"nfcUnsupported": "このデバイスはNFCに対応していません",
"amountToDeposit": "入金額:",
"descriptionOptional": "説明(任意):",
@@ -296,7 +310,7 @@
}
},
"close": "閉じる",
"aboutDescription": "キューバのDNAを持つ世界のためのCashuウォレット。La Chispaの兄弟。",
"aboutDescription": "キューバのDNAを持つ世界のためのCashuウォレット。LaChispaの兄弟。",
"couldNotOpenLink": "リンクを開けませんでした",
"deleteWalletQuestion": "ウォレットを削除しますか?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "無効なnsec",
"p2pkErrorKeyAlreadyExists": "この鍵は既に存在します",
"p2pkErrorKeyNotFound": "鍵が見つかりません",
"p2pkErrorCannotDeletePrimary": "プライマリ鍵は削除できません",
"p2pkSendComingSoon": "近日公開"
"p2pkErrorCannotDeletePrimary": "プライマリ鍵は削除できません"
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "수신자가 청구할 때까지 이 토큰을 보관하세요. 분실하면 자금을 잃게 됩니다.",
"tokenCopiedToClipboard": "토큰이 클립보드에 복사되었습니다",
"copyAsEmoji": "이모지로 복사",
"emojiCopiedToClipboard": "토큰이 이모지로 복사되었습니다 🥜",
"peanutDecodeError": "이모지 토큰을 디코딩할 수 없습니다. 손상되었을 수 있습니다.",
"nfcWrite": "NFC 태그에 쓰기",
"nfcRead": "NFC 태그 읽기",
"nfcHoldNear": "기기를 NFC 태그에 가까이 대세요...",
"nfcWriteSuccess": "토큰이 NFC 태그에 기록되었습니다",
"nfcWriteError": "NFC 쓰기 오류: {error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "NFC 읽기 오류: {error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFC가 비활성화되어 있습니다. 설정에서 활성화하세요.",
"nfcUnsupported": "이 기기는 NFC를 지원하지 않습니다",
"amountToDeposit": "입금할 금액:",
"descriptionOptional": "설명 (선택사항):",
@@ -296,7 +310,7 @@
}
},
"close": "닫기",
"aboutDescription": "전 세계를 위한 쿠바 DNA를 가진 Cashu 지갑. La Chispa의 형제.",
"aboutDescription": "전 세계를 위한 쿠바 DNA를 가진 Cashu 지갑. LaChispa의 형제.",
"couldNotOpenLink": "링크를 열 수 없음",
"deleteWalletQuestion": "지갑을 삭제하시겠습니까?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "유효하지 않은 nsec",
"p2pkErrorKeyAlreadyExists": "이 키는 이미 존재합니다",
"p2pkErrorKeyNotFound": "키를 찾을 수 없습니다",
"p2pkErrorCannotDeletePrimary": "기본 키는 삭제할 수 없습니다",
"p2pkSendComingSoon": "곧 출시 예정"
"p2pkErrorCannotDeletePrimary": "기본 키는 삭제할 수 없습니다"
}
+119 -30
View File
@@ -71,7 +71,8 @@ import 'app_localizations_zh.dart';
/// be consistent with the languages listed in the L10n.supportedLocales
/// property.
abstract class L10n {
L10n(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
L10n(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
@@ -91,12 +92,13 @@ abstract class L10n {
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
@@ -110,7 +112,7 @@ abstract class L10n {
Locale('pt'),
Locale('ru'),
Locale('sw'),
Locale('zh')
Locale('zh'),
];
/// No description provided for @appName.
@@ -707,6 +709,72 @@ abstract class L10n {
/// **'Token copiado al portapapeles'**
String get tokenCopiedToClipboard;
/// No description provided for @copyAsEmoji.
///
/// In es, this message translates to:
/// **'Copiar como emoji'**
String get copyAsEmoji;
/// No description provided for @emojiCopiedToClipboard.
///
/// In es, this message translates to:
/// **'Token copiado como emoji 🥜'**
String get emojiCopiedToClipboard;
/// No description provided for @peanutDecodeError.
///
/// In es, this message translates to:
/// **'No se pudo decodificar el token emoji. Puede estar corrupto.'**
String get peanutDecodeError;
/// No description provided for @nfcWrite.
///
/// In es, this message translates to:
/// **'Escribir en tag NFC'**
String get nfcWrite;
/// No description provided for @nfcRead.
///
/// In es, this message translates to:
/// **'Leer tag NFC'**
String get nfcRead;
/// No description provided for @nfcHoldNear.
///
/// In es, this message translates to:
/// **'Acerca el dispositivo al tag NFC...'**
String get nfcHoldNear;
/// No description provided for @nfcWriteSuccess.
///
/// In es, this message translates to:
/// **'Token escrito en tag NFC'**
String get nfcWriteSuccess;
/// No description provided for @nfcWriteError.
///
/// In es, this message translates to:
/// **'Error NFC al escribir: {error}'**
String nfcWriteError(String error);
/// No description provided for @nfcReadError.
///
/// In es, this message translates to:
/// **'Error NFC al leer: {error}'**
String nfcReadError(String error);
/// No description provided for @nfcDisabled.
///
/// In es, this message translates to:
/// **'NFC está desactivado. Actívalo en Ajustes.'**
String get nfcDisabled;
/// No description provided for @nfcUnsupported.
///
/// In es, this message translates to:
/// **'Este dispositivo no soporta NFC'**
String get nfcUnsupported;
/// No description provided for @amountToDeposit.
///
/// In es, this message translates to:
@@ -1376,7 +1444,7 @@ abstract class L10n {
/// No description provided for @aboutDescription.
///
/// In es, this message translates to:
/// **'Un wallet de Cashu con ADN cubano para el mundo entero. Hermano de La Chispa.'**
/// **'Un wallet de Cashu con ADN cubano para el mundo entero. Hermano de LaChispa.'**
String get aboutDescription;
/// No description provided for @couldNotOpenLink.
@@ -1721,6 +1789,12 @@ abstract class L10n {
/// **'Todo'**
String get selectAll;
/// No description provided for @deselectAll.
///
/// In es, this message translates to:
/// **'Ninguno'**
String get deselectAll;
/// No description provided for @selectNotesToSend.
///
/// In es, this message translates to:
@@ -2140,12 +2214,6 @@ abstract class L10n {
/// In es, this message translates to:
/// **'No se puede eliminar la clave principal'**
String get p2pkErrorCannotDeletePrimary;
/// No description provided for @p2pkSendComingSoon.
///
/// In es, this message translates to:
/// **'Disponible próximamente'**
String get p2pkSendComingSoon;
}
class _L10nDelegate extends LocalizationsDelegate<L10n> {
@@ -2157,34 +2225,55 @@ class _L10nDelegate extends LocalizationsDelegate<L10n> {
}
@override
bool isSupported(Locale locale) => <String>['de', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'pt', 'ru', 'sw', 'zh'].contains(locale.languageCode);
bool isSupported(Locale locale) => <String>[
'de',
'en',
'es',
'fr',
'it',
'ja',
'ko',
'pt',
'ru',
'sw',
'zh',
].contains(locale.languageCode);
@override
bool shouldReload(_L10nDelegate old) => false;
}
L10n lookupL10n(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'de': return L10nDe();
case 'en': return L10nEn();
case 'es': return L10nEs();
case 'fr': return L10nFr();
case 'it': return L10nIt();
case 'ja': return L10nJa();
case 'ko': return L10nKo();
case 'pt': return L10nPt();
case 'ru': return L10nRu();
case 'sw': return L10nSw();
case 'zh': return L10nZh();
case 'de':
return L10nDe();
case 'en':
return L10nEn();
case 'es':
return L10nEs();
case 'fr':
return L10nFr();
case 'it':
return L10nIt();
case 'ja':
return L10nJa();
case 'ko':
return L10nKo();
case 'pt':
return L10nPt();
case 'ru':
return L10nRu();
case 'sw':
return L10nSw();
case 'zh':
return L10nZh();
}
throw FlutterError(
'L10n.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.'
'that was used.',
);
}
+111 -38
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -60,7 +60,8 @@ class L10nDe extends L10n {
String get generatingSeed => 'Sichere Generierung deiner Seed-Phrase';
@override
String get createWalletDescription => 'Eine 12-Wort Seed-Phrase wird generiert.\nBewahre sie an einem sicheren Ort auf.';
String get createWalletDescription =>
'Eine 12-Wort Seed-Phrase wird generiert.\nBewahre sie an einem sicheren Ort auf.';
@override
String get generateWallet => 'Wallet generieren';
@@ -69,10 +70,12 @@ class L10nDe extends L10n {
String get walletCreated => 'Wallet erstellt!';
@override
String get walletCreatedDescription => 'Deine Wallet ist bereit. Wir empfehlen, jetzt ein Backup deiner Seed-Phrase zu erstellen.';
String get walletCreatedDescription =>
'Deine Wallet ist bereit. Wir empfehlen, jetzt ein Backup deiner Seed-Phrase zu erstellen.';
@override
String get backupWarning => 'Ohne Backup verlierst du den Zugang zu deinen Mitteln, wenn du das Gerät verlierst.';
String get backupWarning =>
'Ohne Backup verlierst du den Zugang zu deinen Mitteln, wenn du das Gerät verlierst.';
@override
String get backupNow => 'Jetzt sichern';
@@ -87,13 +90,15 @@ class L10nDe extends L10n {
String get seedPhraseTitle => 'Deine Seed-Phrase';
@override
String get seedPhraseDescription => 'Speichere diese 12 Wörter in der richtigen Reihenfolge. Sie sind der einzige Weg, deine Wallet wiederherzustellen.';
String get seedPhraseDescription =>
'Speichere diese 12 Wörter in der richtigen Reihenfolge. Sie sind der einzige Weg, deine Wallet wiederherzustellen.';
@override
String get revealSeedPhrase => 'Seed-Phrase anzeigen';
@override
String get tapToReveal => 'Tippe auf den Button, um\ndeine Seed-Phrase anzuzeigen';
String get tapToReveal =>
'Tippe auf den Button, um\ndeine Seed-Phrase anzuzeigen';
@override
String get copyToClipboard => 'In Zwischenablage kopieren';
@@ -105,7 +110,8 @@ class L10nDe extends L10n {
String get neverShareSeed => 'Teile deine Seed-Phrase niemals mit anderen.';
@override
String get confirmBackup => 'Ich habe meine Seed-Phrase an einem sicheren Ort gespeichert';
String get confirmBackup =>
'Ich habe meine Seed-Phrase an einem sicheren Ort gespeichert';
@override
String get continue_ => 'Weiter';
@@ -117,7 +123,8 @@ class L10nDe extends L10n {
String get enterSeedPhrase => 'Gib deine Seed-Phrase ein';
@override
String get enterSeedDescription => 'Gib die 12 oder 24 Wörter durch Leerzeichen getrennt ein.';
String get enterSeedDescription =>
'Gib die 12 oder 24 Wörter durch Leerzeichen getrennt ein.';
@override
String get seedPlaceholder => 'wort1 wort2 wort3 ...';
@@ -131,7 +138,8 @@ class L10nDe extends L10n {
String get needWords => '(du brauchst 12 oder 24)';
@override
String get restoreScanningMint => 'Mint wird nach vorhandenen Token durchsucht...';
String get restoreScanningMint =>
'Mint wird nach vorhandenen Token durchsucht...';
@override
String restoreError(String error) {
@@ -310,11 +318,51 @@ class L10nDe extends L10n {
}
@override
String get keepTokenWarning => 'Bewahre diesen Token auf, bis der Empfänger ihn einlöst. Wenn du ihn verlierst, verlierst du die Mittel.';
String get keepTokenWarning =>
'Bewahre diesen Token auf, bis der Empfänger ihn einlöst. Wenn du ihn verlierst, verlierst du die Mittel.';
@override
String get tokenCopiedToClipboard => 'Token in Zwischenablage kopiert';
@override
String get copyAsEmoji => 'Als Emoji kopieren';
@override
String get emojiCopiedToClipboard => 'Token als Emoji kopiert 🥜';
@override
String get peanutDecodeError =>
'Emoji-Token konnte nicht dekodiert werden. Möglicherweise beschädigt.';
@override
String get nfcWrite => 'Auf NFC-Tag schreiben';
@override
String get nfcRead => 'NFC-Tag lesen';
@override
String get nfcHoldNear => 'Gerät an NFC-Tag halten...';
@override
String get nfcWriteSuccess => 'Token auf NFC-Tag geschrieben';
@override
String nfcWriteError(String error) {
return 'NFC-Schreibfehler: $error';
}
@override
String nfcReadError(String error) {
return 'NFC-Lesefehler: $error';
}
@override
String get nfcDisabled =>
'NFC ist deaktiviert. Aktiviere es in den Einstellungen.';
@override
String get nfcUnsupported => 'Dieses Gerät unterstützt kein NFC';
@override
String get amountToDeposit => 'Einzuzahlender Betrag:';
@@ -450,7 +498,8 @@ class L10nDe extends L10n {
String get noPendingTransactions => 'Keine ausstehenden Transaktionen';
@override
String get allTransactionsCompleted => 'Alle deine Transaktionen sind abgeschlossen';
String get allTransactionsCompleted =>
'Alle deine Transaktionen sind abgeschlossen';
@override
String get noEcashTransactions => 'Keine Ecash Transaktionen';
@@ -462,7 +511,8 @@ class L10nDe extends L10n {
String get noLightningTransactions => 'Keine Lightning Transaktionen';
@override
String get depositOrWithdrawLightning => 'Zahle ein oder hebe ab via Lightning';
String get depositOrWithdrawLightning =>
'Zahle ein oder hebe ab via Lightning';
@override
String get pendingStatus => 'Ausstehend';
@@ -665,7 +715,8 @@ class L10nDe extends L10n {
String get close => 'Schließen';
@override
String get aboutDescription => 'Eine Cashu Wallet mit kubanischer DNA für die ganze Welt. Bruder von La Chispa.';
String get aboutDescription =>
'Eine Cashu Wallet mit kubanischer DNA für die ganze Welt. Bruder von LaChispa.';
@override
String get couldNotOpenLink => 'Link konnte nicht geöffnet werden';
@@ -677,7 +728,8 @@ class L10nDe extends L10n {
String get actionIrreversible => 'Diese Aktion ist unwiderruflich';
@override
String get deleteWalletWarning => 'Alle Daten werden gelöscht, einschließlich deiner Seed-Phrase und Tokens. Stelle sicher, dass du ein Backup hast.';
String get deleteWalletWarning =>
'Alle Daten werden gelöscht, einschließlich deiner Seed-Phrase und Tokens. Stelle sicher, dass du ein Backup hast.';
@override
String get typeDeleteToConfirm => 'Gib \"LÖSCHEN\" zur Bestätigung ein:';
@@ -694,19 +746,22 @@ class L10nDe extends L10n {
String get recoverTokensTitle => 'Tokens wiederherstellen';
@override
String get recoverTokensDescription => 'Mints scannen, um Tokens wiederherzustellen, die mit deiner Seed-Phrase verknüpft sind (NUT-13)';
String get recoverTokensDescription =>
'Mints scannen, um Tokens wiederherzustellen, die mit deiner Seed-Phrase verknüpft sind (NUT-13)';
@override
String get useCurrentSeedPhrase => 'Meine aktuelle Seed-Phrase verwenden';
@override
String get scanWithSavedWords => 'Mints mit den gespeicherten 12 Wörtern scannen';
String get scanWithSavedWords =>
'Mints mit den gespeicherten 12 Wörtern scannen';
@override
String get useOtherSeedPhrase => 'Andere Seed-Phrase verwenden';
@override
String get recoverFromOtherWords => 'Tokens von anderen 12 Wörtern wiederherstellen';
String get recoverFromOtherWords =>
'Tokens von anderen 12 Wörtern wiederherstellen';
@override
String get mintsToScan => 'Zu scannende Mints:';
@@ -720,7 +775,8 @@ class L10nDe extends L10n {
String get specificMint => 'Ein bestimmter Mint';
@override
String get enterMnemonicWords => 'Gib die 12 Wörter durch Leerzeichen getrennt ein...';
String get enterMnemonicWords =>
'Gib die 12 Wörter durch Leerzeichen getrennt ein...';
@override
String get scanMints => 'Mints scannen';
@@ -740,7 +796,8 @@ class L10nDe extends L10n {
}
@override
String get scanCompleteNoTokens => 'Scan abgeschlossen. Keine neuen Tokens gefunden.';
String get scanCompleteNoTokens =>
'Scan abgeschlossen. Keine neuen Tokens gefunden.';
@override
String mintsWithError(int count) {
@@ -763,7 +820,8 @@ class L10nDe extends L10n {
}
@override
String get noTokensForMnemonic => 'Keine Tokens mit diesem Mnemonic verknüpft gefunden.';
String get noTokensForMnemonic =>
'Keine Tokens mit diesem Mnemonic verknüpft gefunden.';
@override
String get noConnectedMints => 'Keine verbundenen Mints';
@@ -843,7 +901,8 @@ class L10nDe extends L10n {
String get deleteMintConfirmTitle => 'Mint löschen';
@override
String get deleteMintConfirmMessage => 'Wenn du Guthaben auf diesem Mint hast, geht es verloren. Bist du sicher?';
String get deleteMintConfirmMessage =>
'Wenn du Guthaben auf diesem Mint hast, geht es verloren. Bist du sicher?';
@override
String get delete => 'Löschen';
@@ -854,6 +913,9 @@ class L10nDe extends L10n {
@override
String get selectAll => 'Alle';
@override
String get deselectAll => 'Keine';
@override
String get selectNotesToSend => 'Wähle die zu sendenden Notizen:';
@@ -893,19 +955,24 @@ class L10nDe extends L10n {
String get tokenSavedForLater => 'Token zum späteren Einlösen gespeichert';
@override
String get noConnectionTokenSaved => 'Keine Verbindung. Token zum späteren Einlösen gespeichert.';
String get noConnectionTokenSaved =>
'Keine Verbindung. Token zum späteren Einlösen gespeichert.';
@override
String get unknownMintOffline => 'Dieser Token stammt von einem unbekannten Mint. Verbinde dich mit dem Internet, um ihn hinzuzufügen und den Token einzulösen.';
String get unknownMintOffline =>
'Dieser Token stammt von einem unbekannten Mint. Verbinde dich mit dem Internet, um ihn hinzuzufügen und den Token einzulösen.';
@override
String get noConnectionTryLater => 'Keine Verbindung zum Mint. Versuche es später erneut.';
String get noConnectionTryLater =>
'Keine Verbindung zum Mint. Versuche es später erneut.';
@override
String get saveTokenError => 'Fehler beim Speichern des Tokens. Bitte erneut versuchen.';
String get saveTokenError =>
'Fehler beim Speichern des Tokens. Bitte erneut versuchen.';
@override
String get pendingTokenLimitReached => 'Limit für ausstehende Tokens erreicht (max 50)';
String get pendingTokenLimitReached =>
'Limit für ausstehende Tokens erreicht (max 50)';
@override
String get filterToReceive => 'Zu empfangen';
@@ -982,10 +1049,12 @@ class L10nDe extends L10n {
String get unrecognizedQrCode => 'Nicht erkannter QR-Code';
@override
String get scanCashuTokenHint => 'Scanne einen Cashu Token (cashuA... oder cashuB...)';
String get scanCashuTokenHint =>
'Scanne einen Cashu Token (cashuA... oder cashuB...)';
@override
String get scanLightningInvoiceHint => 'Scanne eine Lightning Rechnung (lnbc...)';
String get scanLightningInvoiceHint =>
'Scanne eine Lightning Rechnung (lnbc...)';
@override
String get addMintQuestion => 'Diesen Mint hinzufügen?';
@@ -994,7 +1063,8 @@ class L10nDe extends L10n {
String get cameraPermissionDenied => 'Kamera-Berechtigung verweigert';
@override
String get paymentRequestNotSupported => 'Zahlungsanfragen werden noch nicht unterstützt';
String get paymentRequestNotSupported =>
'Zahlungsanfragen werden noch nicht unterstützt';
@override
String get p2pkTitle => 'P2PK-Schlüssel';
@@ -1003,10 +1073,12 @@ class L10nDe extends L10n {
String get p2pkSettingsDescription => 'Gesperrtes ecash empfangen';
@override
String get p2pkExperimental => 'P2PK ist experimentell. Mit Vorsicht verwenden.';
String get p2pkExperimental =>
'P2PK ist experimentell. Mit Vorsicht verwenden.';
@override
String get p2pkPendingSendWarning => 'Du hast einen ausstehenden P2PK-Versand. Gehe zum Verlauf und aktualisiere, nachdem der Empfänger den Token eingelöst hat.';
String get p2pkPendingSendWarning =>
'Du hast einen ausstehenden P2PK-Versand. Gehe zum Verlauf und aktualisiere, nachdem der Empfänger den Token eingelöst hat.';
@override
String get p2pkExperimentalShort => 'Experimentell';
@@ -1063,7 +1135,8 @@ class L10nDe extends L10n {
String get p2pkLockedToOther => 'Für anderen Schlüssel gesperrt';
@override
String get p2pkCannotUnlock => 'Du hast nicht den Schlüssel, um diesen Token zu entsperren';
String get p2pkCannotUnlock =>
'Du hast nicht den Schlüssel, um diesen Token zu entsperren';
@override
String get p2pkEnterPrivateKey => 'Privaten Schlüssel eingeben (nsec)';
@@ -1072,13 +1145,15 @@ class L10nDe extends L10n {
String get p2pkDeleteTitle => 'Schlüssel löschen';
@override
String get p2pkDeleteConfirm => 'Diesen Schlüssel löschen? Du kannst keine Token mehr empfangen, die daran gesperrt sind.';
String get p2pkDeleteConfirm =>
'Diesen Schlüssel löschen? Du kannst keine Token mehr empfangen, die daran gesperrt sind.';
@override
String get p2pkRequiresConnection => 'P2PK erfordert Verbindung zum Mint';
@override
String get p2pkErrorMaxKeysReached => 'Maximale Anzahl importierter Schlüssel erreicht (10)';
String get p2pkErrorMaxKeysReached =>
'Maximale Anzahl importierter Schlüssel erreicht (10)';
@override
String get p2pkErrorInvalidNsec => 'Ungültiger nsec';
@@ -1090,8 +1165,6 @@ class L10nDe extends L10n {
String get p2pkErrorKeyNotFound => 'Schlüssel nicht gefunden';
@override
String get p2pkErrorCannotDeletePrimary => 'Primärschlüssel kann nicht gelöscht werden';
@override
String get p2pkSendComingSoon => 'Demnächst verfügbar';
String get p2pkErrorCannotDeletePrimary =>
'Primärschlüssel kann nicht gelöscht werden';
}
+78 -22
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -60,7 +60,8 @@ class L10nEn extends L10n {
String get generatingSeed => 'Generating your seed phrase securely';
@override
String get createWalletDescription => 'A 12-word seed phrase will be generated.\nStore it in a safe place.';
String get createWalletDescription =>
'A 12-word seed phrase will be generated.\nStore it in a safe place.';
@override
String get generateWallet => 'Generate wallet';
@@ -69,10 +70,12 @@ class L10nEn extends L10n {
String get walletCreated => 'Wallet created!';
@override
String get walletCreatedDescription => 'Your wallet is ready. We recommend backing up your seed phrase now.';
String get walletCreatedDescription =>
'Your wallet is ready. We recommend backing up your seed phrase now.';
@override
String get backupWarning => 'Without backup, you will lose access to your funds if you lose the device.';
String get backupWarning =>
'Without backup, you will lose access to your funds if you lose the device.';
@override
String get backupNow => 'Backup now';
@@ -87,7 +90,8 @@ class L10nEn extends L10n {
String get seedPhraseTitle => 'Your seed phrase';
@override
String get seedPhraseDescription => 'Save these 12 words in order. They are the only way to recover your wallet.';
String get seedPhraseDescription =>
'Save these 12 words in order. They are the only way to recover your wallet.';
@override
String get revealSeedPhrase => 'Reveal seed phrase';
@@ -117,7 +121,8 @@ class L10nEn extends L10n {
String get enterSeedPhrase => 'Enter your seed phrase';
@override
String get enterSeedDescription => 'Type the 12 or 24 words separated by spaces.';
String get enterSeedDescription =>
'Type the 12 or 24 words separated by spaces.';
@override
String get seedPlaceholder => 'word1 word2 word3 ...';
@@ -310,11 +315,50 @@ class L10nEn extends L10n {
}
@override
String get keepTokenWarning => 'Keep this token until the recipient claims it. If you lose it, you will lose the funds.';
String get keepTokenWarning =>
'Keep this token until the recipient claims it. If you lose it, you will lose the funds.';
@override
String get tokenCopiedToClipboard => 'Token copied to clipboard';
@override
String get copyAsEmoji => 'Copy as emoji';
@override
String get emojiCopiedToClipboard => 'Token copied as emoji 🥜';
@override
String get peanutDecodeError =>
'Could not decode emoji token. It may be corrupted.';
@override
String get nfcWrite => 'Write to NFC tag';
@override
String get nfcRead => 'Read NFC tag';
@override
String get nfcHoldNear => 'Hold device near NFC tag...';
@override
String get nfcWriteSuccess => 'Token written to NFC tag';
@override
String nfcWriteError(String error) {
return 'NFC write error: $error';
}
@override
String nfcReadError(String error) {
return 'NFC read error: $error';
}
@override
String get nfcDisabled => 'NFC is disabled. Enable it in Settings.';
@override
String get nfcUnsupported => 'This device does not support NFC';
@override
String get amountToDeposit => 'Amount to deposit:';
@@ -665,7 +709,8 @@ class L10nEn extends L10n {
String get close => 'Close';
@override
String get aboutDescription => 'A Cashu wallet with Cuban DNA for the entire world. Brother of La Chispa.';
String get aboutDescription =>
'A Cashu wallet with Cuban DNA for the entire world. Brother of LaChispa.';
@override
String get couldNotOpenLink => 'Could not open link';
@@ -677,7 +722,8 @@ class L10nEn extends L10n {
String get actionIrreversible => 'This action is irreversible';
@override
String get deleteWalletWarning => 'All data will be deleted including your seed phrase and tokens. Make sure you have a backup.';
String get deleteWalletWarning =>
'All data will be deleted including your seed phrase and tokens. Make sure you have a backup.';
@override
String get typeDeleteToConfirm => 'Type \"DELETE\" to confirm:';
@@ -694,7 +740,8 @@ class L10nEn extends L10n {
String get recoverTokensTitle => 'Recover tokens';
@override
String get recoverTokensDescription => 'Scan mints to recover tokens associated with your seed phrase (NUT-13)';
String get recoverTokensDescription =>
'Scan mints to recover tokens associated with your seed phrase (NUT-13)';
@override
String get useCurrentSeedPhrase => 'Use my current seed phrase';
@@ -763,7 +810,8 @@ class L10nEn extends L10n {
}
@override
String get noTokensForMnemonic => 'No tokens found associated with that mnemonic.';
String get noTokensForMnemonic =>
'No tokens found associated with that mnemonic.';
@override
String get noConnectedMints => 'No connected mints';
@@ -843,7 +891,8 @@ class L10nEn extends L10n {
String get deleteMintConfirmTitle => 'Delete mint';
@override
String get deleteMintConfirmMessage => 'If you have balance in this mint, it will be lost. Are you sure?';
String get deleteMintConfirmMessage =>
'If you have balance in this mint, it will be lost. Are you sure?';
@override
String get delete => 'Delete';
@@ -854,6 +903,9 @@ class L10nEn extends L10n {
@override
String get selectAll => 'All';
@override
String get deselectAll => 'None';
@override
String get selectNotesToSend => 'Select the notes you want to send:';
@@ -893,10 +945,12 @@ class L10nEn extends L10n {
String get tokenSavedForLater => 'Token saved to claim later';
@override
String get noConnectionTokenSaved => 'No connection. Token saved to claim later.';
String get noConnectionTokenSaved =>
'No connection. Token saved to claim later.';
@override
String get unknownMintOffline => 'This token is from an unknown mint. Connect to the internet to add it and claim the token.';
String get unknownMintOffline =>
'This token is from an unknown mint. Connect to the internet to add it and claim the token.';
@override
String get noConnectionTryLater => 'No connection to mint. Try again later.';
@@ -905,7 +959,8 @@ class L10nEn extends L10n {
String get saveTokenError => 'Error saving token. Please try again.';
@override
String get pendingTokenLimitReached => 'Pending tokens limit reached (max 50)';
String get pendingTokenLimitReached =>
'Pending tokens limit reached (max 50)';
@override
String get filterToReceive => 'To receive';
@@ -982,7 +1037,8 @@ class L10nEn extends L10n {
String get unrecognizedQrCode => 'Unrecognized QR code';
@override
String get scanCashuTokenHint => 'Scan a Cashu token (cashuA... or cashuB...)';
String get scanCashuTokenHint =>
'Scan a Cashu token (cashuA... or cashuB...)';
@override
String get scanLightningInvoiceHint => 'Scan a Lightning invoice (lnbc...)';
@@ -994,7 +1050,8 @@ class L10nEn extends L10n {
String get cameraPermissionDenied => 'Camera permission denied';
@override
String get paymentRequestNotSupported => 'Payment requests are not yet supported';
String get paymentRequestNotSupported =>
'Payment requests are not yet supported';
@override
String get p2pkTitle => 'P2PK Keys';
@@ -1006,7 +1063,8 @@ class L10nEn extends L10n {
String get p2pkExperimental => 'P2PK is experimental. Use with caution.';
@override
String get p2pkPendingSendWarning => 'You have a pending P2PK send. Go to history and refresh after the recipient claims the token.';
String get p2pkPendingSendWarning =>
'You have a pending P2PK send. Go to history and refresh after the recipient claims the token.';
@override
String get p2pkExperimentalShort => 'Experimental';
@@ -1072,7 +1130,8 @@ class L10nEn extends L10n {
String get p2pkDeleteTitle => 'Delete key';
@override
String get p2pkDeleteConfirm => 'Delete this key? You won\'t be able to receive tokens locked to it.';
String get p2pkDeleteConfirm =>
'Delete this key? You won\'t be able to receive tokens locked to it.';
@override
String get p2pkRequiresConnection => 'P2PK requires connection to the mint';
@@ -1091,7 +1150,4 @@ class L10nEn extends L10n {
@override
String get p2pkErrorCannotDeletePrimary => 'Cannot delete primary key';
@override
String get p2pkSendComingSoon => 'Coming soon';
}
+94 -30
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -60,7 +60,8 @@ class L10nEs extends L10n {
String get generatingSeed => 'Generando tu frase semilla de forma segura';
@override
String get createWalletDescription => 'Se generará una frase semilla de 12 palabras.\nGuárdala en un lugar seguro.';
String get createWalletDescription =>
'Se generará una frase semilla de 12 palabras.\nGuárdala en un lugar seguro.';
@override
String get generateWallet => 'Generar wallet';
@@ -69,10 +70,12 @@ class L10nEs extends L10n {
String get walletCreated => '¡Wallet creada!';
@override
String get walletCreatedDescription => 'Tu wallet está lista. Te recomendamos hacer backup de tu frase semilla ahora.';
String get walletCreatedDescription =>
'Tu wallet está lista. Te recomendamos hacer backup de tu frase semilla ahora.';
@override
String get backupWarning => 'Sin backup, perderás acceso a tus fondos si pierdes el dispositivo.';
String get backupWarning =>
'Sin backup, perderás acceso a tus fondos si pierdes el dispositivo.';
@override
String get backupNow => 'Hacer backup ahora';
@@ -87,7 +90,8 @@ class L10nEs extends L10n {
String get seedPhraseTitle => 'Tu frase semilla';
@override
String get seedPhraseDescription => 'Guarda estas 12 palabras en orden. Son la única forma de recuperar tu wallet.';
String get seedPhraseDescription =>
'Guarda estas 12 palabras en orden. Son la única forma de recuperar tu wallet.';
@override
String get revealSeedPhrase => 'Revelar frase semilla';
@@ -117,7 +121,8 @@ class L10nEs extends L10n {
String get enterSeedPhrase => 'Ingresa tu frase semilla';
@override
String get enterSeedDescription => 'Escribe las 12 o 24 palabras separadas por espacios.';
String get enterSeedDescription =>
'Escribe las 12 o 24 palabras separadas por espacios.';
@override
String get seedPlaceholder => 'palabra1 palabra2 palabra3 ...';
@@ -310,11 +315,50 @@ class L10nEs extends L10n {
}
@override
String get keepTokenWarning => 'Guarda este token hasta que el receptor lo reclame. Si lo pierdes, perderás los fondos.';
String get keepTokenWarning =>
'Guarda este token hasta que el receptor lo reclame. Si lo pierdes, perderás los fondos.';
@override
String get tokenCopiedToClipboard => 'Token copiado al portapapeles';
@override
String get copyAsEmoji => 'Copiar como emoji';
@override
String get emojiCopiedToClipboard => 'Token copiado como emoji 🥜';
@override
String get peanutDecodeError =>
'No se pudo decodificar el token emoji. Puede estar corrupto.';
@override
String get nfcWrite => 'Escribir en tag NFC';
@override
String get nfcRead => 'Leer tag NFC';
@override
String get nfcHoldNear => 'Acerca el dispositivo al tag NFC...';
@override
String get nfcWriteSuccess => 'Token escrito en tag NFC';
@override
String nfcWriteError(String error) {
return 'Error NFC al escribir: $error';
}
@override
String nfcReadError(String error) {
return 'Error NFC al leer: $error';
}
@override
String get nfcDisabled => 'NFC está desactivado. Actívalo en Ajustes.';
@override
String get nfcUnsupported => 'Este dispositivo no soporta NFC';
@override
String get amountToDeposit => 'Monto a depositar:';
@@ -450,7 +494,8 @@ class L10nEs extends L10n {
String get noPendingTransactions => 'Sin transacciones pendientes';
@override
String get allTransactionsCompleted => 'Todas tus transacciones están completadas';
String get allTransactionsCompleted =>
'Todas tus transacciones están completadas';
@override
String get noEcashTransactions => 'Sin transacciones Ecash';
@@ -665,7 +710,8 @@ class L10nEs extends L10n {
String get close => 'Cerrar';
@override
String get aboutDescription => 'Un wallet de Cashu con ADN cubano para el mundo entero. Hermano de La Chispa.';
String get aboutDescription =>
'Un wallet de Cashu con ADN cubano para el mundo entero. Hermano de LaChispa.';
@override
String get couldNotOpenLink => 'No se pudo abrir el enlace';
@@ -677,7 +723,8 @@ class L10nEs extends L10n {
String get actionIrreversible => 'Esta acción es irreversible';
@override
String get deleteWalletWarning => 'Se eliminarán todos los datos incluyendo tu seed phrase y tokens. Asegúrate de tener un backup.';
String get deleteWalletWarning =>
'Se eliminarán todos los datos incluyendo tu seed phrase y tokens. Asegúrate de tener un backup.';
@override
String get typeDeleteToConfirm => 'Escribe \"BORRAR\" para confirmar:';
@@ -694,13 +741,15 @@ class L10nEs extends L10n {
String get recoverTokensTitle => 'Recuperar tokens';
@override
String get recoverTokensDescription => 'Escanea los mints para recuperar tokens asociados a tu seed phrase (NUT-13)';
String get recoverTokensDescription =>
'Escanea los mints para recuperar tokens asociados a tu seed phrase (NUT-13)';
@override
String get useCurrentSeedPhrase => 'Usar mi seed phrase actual';
@override
String get scanWithSavedWords => 'Escanear mints con las 12 palabras guardadas';
String get scanWithSavedWords =>
'Escanear mints con las 12 palabras guardadas';
@override
String get useOtherSeedPhrase => 'Usar otra seed phrase';
@@ -720,7 +769,8 @@ class L10nEs extends L10n {
String get specificMint => 'Un mint específico';
@override
String get enterMnemonicWords => 'Ingresa las 12 palabras separadas por espacios...';
String get enterMnemonicWords =>
'Ingresa las 12 palabras separadas por espacios...';
@override
String get scanMints => 'Escanear mints';
@@ -740,7 +790,8 @@ class L10nEs extends L10n {
}
@override
String get scanCompleteNoTokens => 'Escaneo completado. No se encontraron tokens nuevos.';
String get scanCompleteNoTokens =>
'Escaneo completado. No se encontraron tokens nuevos.';
@override
String mintsWithError(int count) {
@@ -763,7 +814,8 @@ class L10nEs extends L10n {
}
@override
String get noTokensForMnemonic => 'No se encontraron tokens asociados a ese mnemonic.';
String get noTokensForMnemonic =>
'No se encontraron tokens asociados a ese mnemonic.';
@override
String get noConnectedMints => 'No hay mints conectados';
@@ -843,7 +895,8 @@ class L10nEs extends L10n {
String get deleteMintConfirmTitle => 'Eliminar mint';
@override
String get deleteMintConfirmMessage => 'Si tienes balance en este mint, se perderá. ¿Estás seguro?';
String get deleteMintConfirmMessage =>
'Si tienes balance en este mint, se perderá. ¿Estás seguro?';
@override
String get delete => 'Eliminar';
@@ -854,6 +907,9 @@ class L10nEs extends L10n {
@override
String get selectAll => 'Todo';
@override
String get deselectAll => 'Ninguno';
@override
String get selectNotesToSend => 'Selecciona las notas que deseas enviar:';
@@ -893,10 +949,12 @@ class L10nEs extends L10n {
String get tokenSavedForLater => 'Token guardado para reclamar después';
@override
String get noConnectionTokenSaved => 'Sin conexión. Token guardado para reclamar después.';
String get noConnectionTokenSaved =>
'Sin conexión. Token guardado para reclamar después.';
@override
String get unknownMintOffline => 'Este token es de un mint desconocido. Conéctate a internet para agregarlo y reclamar el token.';
String get unknownMintOffline =>
'Este token es de un mint desconocido. Conéctate a internet para agregarlo y reclamar el token.';
@override
String get noConnectionTryLater => 'Sin conexión al mint. Intenta más tarde.';
@@ -905,7 +963,8 @@ class L10nEs extends L10n {
String get saveTokenError => 'Error al guardar el token. Intenta de nuevo.';
@override
String get pendingTokenLimitReached => 'Límite de tokens pendientes alcanzado (max 50)';
String get pendingTokenLimitReached =>
'Límite de tokens pendientes alcanzado (max 50)';
@override
String get filterToReceive => 'Para recibir';
@@ -982,10 +1041,12 @@ class L10nEs extends L10n {
String get unrecognizedQrCode => 'Código QR no reconocido';
@override
String get scanCashuTokenHint => 'Escanea un token Cashu (cashuA... o cashuB...)';
String get scanCashuTokenHint =>
'Escanea un token Cashu (cashuA... o cashuB...)';
@override
String get scanLightningInvoiceHint => 'Escanea un invoice Lightning (lnbc...)';
String get scanLightningInvoiceHint =>
'Escanea un invoice Lightning (lnbc...)';
@override
String get addMintQuestion => '¿Agregar este mint?';
@@ -994,7 +1055,8 @@ class L10nEs extends L10n {
String get cameraPermissionDenied => 'Permiso de cámara denegado';
@override
String get paymentRequestNotSupported => 'Los payment requests aún no están soportados';
String get paymentRequestNotSupported =>
'Los payment requests aún no están soportados';
@override
String get p2pkTitle => 'Claves P2PK';
@@ -1006,7 +1068,8 @@ class L10nEs extends L10n {
String get p2pkExperimental => 'P2PK es experimental. Úsala con precaución.';
@override
String get p2pkPendingSendWarning => 'Tienes un envío P2PK pendiente. Ve al historial y presiona actualizar después de que el destinatario reclame el token.';
String get p2pkPendingSendWarning =>
'Tienes un envío P2PK pendiente. Ve al historial y presiona actualizar después de que el destinatario reclame el token.';
@override
String get p2pkExperimentalShort => 'Experimental';
@@ -1063,7 +1126,8 @@ class L10nEs extends L10n {
String get p2pkLockedToOther => 'Bloqueado para otra clave';
@override
String get p2pkCannotUnlock => 'No tienes la clave para desbloquear este token';
String get p2pkCannotUnlock =>
'No tienes la clave para desbloquear este token';
@override
String get p2pkEnterPrivateKey => 'Ingresa la clave privada (nsec)';
@@ -1072,13 +1136,15 @@ class L10nEs extends L10n {
String get p2pkDeleteTitle => 'Eliminar clave';
@override
String get p2pkDeleteConfirm => '¿Eliminar esta clave? No podrás recibir tokens bloqueados a ella.';
String get p2pkDeleteConfirm =>
'¿Eliminar esta clave? No podrás recibir tokens bloqueados a ella.';
@override
String get p2pkRequiresConnection => 'P2PK requiere conexión al mint';
@override
String get p2pkErrorMaxKeysReached => 'Máximo de claves importadas alcanzado (10)';
String get p2pkErrorMaxKeysReached =>
'Máximo de claves importadas alcanzado (10)';
@override
String get p2pkErrorInvalidNsec => 'nsec inválido';
@@ -1090,8 +1156,6 @@ class L10nEs extends L10n {
String get p2pkErrorKeyNotFound => 'Clave no encontrada';
@override
String get p2pkErrorCannotDeletePrimary => 'No se puede eliminar la clave principal';
@override
String get p2pkSendComingSoon => 'Disponible próximamente';
String get p2pkErrorCannotDeletePrimary =>
'No se puede eliminar la clave principal';
}
+121 -43
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -57,10 +57,12 @@ class L10nFr extends L10n {
String get creatingWallet => 'Création de votre portefeuille...';
@override
String get generatingSeed => 'Génération sécurisée de votre phrase de récupération';
String get generatingSeed =>
'Génération sécurisée de votre phrase de récupération';
@override
String get createWalletDescription => 'Une phrase de récupération de 12 mots sera générée.\nConservez-la dans un endroit sûr.';
String get createWalletDescription =>
'Une phrase de récupération de 12 mots sera générée.\nConservez-la dans un endroit sûr.';
@override
String get generateWallet => 'Générer le portefeuille';
@@ -69,10 +71,12 @@ class L10nFr extends L10n {
String get walletCreated => 'Portefeuille créé !';
@override
String get walletCreatedDescription => 'Votre portefeuille est prêt. Nous vous recommandons de sauvegarder votre phrase de récupération maintenant.';
String get walletCreatedDescription =>
'Votre portefeuille est prêt. Nous vous recommandons de sauvegarder votre phrase de récupération maintenant.';
@override
String get backupWarning => 'Sans sauvegarde, vous perdrez l\'accès à vos fonds si vous perdez l\'appareil.';
String get backupWarning =>
'Sans sauvegarde, vous perdrez l\'accès à vos fonds si vous perdez l\'appareil.';
@override
String get backupNow => 'Sauvegarder maintenant';
@@ -87,13 +91,15 @@ class L10nFr extends L10n {
String get seedPhraseTitle => 'Votre phrase de récupération';
@override
String get seedPhraseDescription => 'Conservez ces 12 mots dans l\'ordre. C\'est le seul moyen de récupérer votre portefeuille.';
String get seedPhraseDescription =>
'Conservez ces 12 mots dans l\'ordre. C\'est le seul moyen de récupérer votre portefeuille.';
@override
String get revealSeedPhrase => 'Révéler la phrase de récupération';
@override
String get tapToReveal => 'Appuyez sur le bouton pour révéler\nvotre phrase de récupération';
String get tapToReveal =>
'Appuyez sur le bouton pour révéler\nvotre phrase de récupération';
@override
String get copyToClipboard => 'Copier dans le presse-papiers';
@@ -102,10 +108,12 @@ class L10nFr extends L10n {
String get seedCopied => 'Phrase copiée dans le presse-papiers';
@override
String get neverShareSeed => 'Ne partagez jamais votre phrase de récupération avec personne.';
String get neverShareSeed =>
'Ne partagez jamais votre phrase de récupération avec personne.';
@override
String get confirmBackup => 'J\'ai sauvegardé ma phrase de récupération dans un endroit sûr';
String get confirmBackup =>
'J\'ai sauvegardé ma phrase de récupération dans un endroit sûr';
@override
String get continue_ => 'Continuer';
@@ -117,7 +125,8 @@ class L10nFr extends L10n {
String get enterSeedPhrase => 'Entrez votre phrase de récupération';
@override
String get enterSeedDescription => 'Tapez les 12 ou 24 mots séparés par des espaces.';
String get enterSeedDescription =>
'Tapez les 12 ou 24 mots séparés par des espaces.';
@override
String get seedPlaceholder => 'mot1 mot2 mot3 ...';
@@ -310,11 +319,51 @@ class L10nFr extends L10n {
}
@override
String get keepTokenWarning => 'Conservez ce token jusqu\'à ce que le destinataire le réclame. Si vous le perdez, vous perdrez les fonds.';
String get keepTokenWarning =>
'Conservez ce token jusqu\'à ce que le destinataire le réclame. Si vous le perdez, vous perdrez les fonds.';
@override
String get tokenCopiedToClipboard => 'Token copié dans le presse-papiers';
@override
String get copyAsEmoji => 'Copier en emoji';
@override
String get emojiCopiedToClipboard => 'Token copié en emoji 🥜';
@override
String get peanutDecodeError =>
'Impossible de décoder le token emoji. Il est peut-être corrompu.';
@override
String get nfcWrite => 'Écrire sur tag NFC';
@override
String get nfcRead => 'Lire tag NFC';
@override
String get nfcHoldNear => 'Approchez l\'appareil du tag NFC...';
@override
String get nfcWriteSuccess => 'Token écrit sur le tag NFC';
@override
String nfcWriteError(String error) {
return 'Erreur NFC écriture : $error';
}
@override
String nfcReadError(String error) {
return 'Erreur NFC lecture : $error';
}
@override
String get nfcDisabled =>
'NFC est désactivé. Activez-le dans les Paramètres.';
@override
String get nfcUnsupported => 'Cet appareil ne prend pas en charge le NFC';
@override
String get amountToDeposit => 'Montant à déposer :';
@@ -364,7 +413,8 @@ class L10nFr extends L10n {
String get description => 'Description :';
@override
String get invoiceCopiedToClipboard => 'Facture copiée dans le presse-papiers';
String get invoiceCopiedToClipboard =>
'Facture copiée dans le presse-papiers';
@override
String deposited(String amount, String unit) {
@@ -450,7 +500,8 @@ class L10nFr extends L10n {
String get noPendingTransactions => 'Aucune transaction en attente';
@override
String get allTransactionsCompleted => 'Toutes vos transactions sont terminées';
String get allTransactionsCompleted =>
'Toutes vos transactions sont terminées';
@override
String get noEcashTransactions => 'Aucune transaction Ecash';
@@ -567,7 +618,8 @@ class L10nFr extends L10n {
String get recoverTokens => 'Récupérer les tokens';
@override
String get scanMintsWithSeed => 'Scanner les mints avec la phrase de récupération';
String get scanMintsWithSeed =>
'Scanner les mints avec la phrase de récupération';
@override
String get appearanceSection => 'LANGUE';
@@ -665,7 +717,8 @@ class L10nFr extends L10n {
String get close => 'Fermer';
@override
String get aboutDescription => 'Un portefeuille Cashu avec ADN cubain pour le monde entier. Frère de La Chispa.';
String get aboutDescription =>
'Un portefeuille Cashu avec ADN cubain pour le monde entier. Frère de LaChispa.';
@override
String get couldNotOpenLink => 'Impossible d\'ouvrir le lien';
@@ -677,7 +730,8 @@ class L10nFr extends L10n {
String get actionIrreversible => 'Cette action est irréversible';
@override
String get deleteWalletWarning => 'Toutes les données seront supprimées, y compris votre phrase de récupération et vos tokens. Assurez-vous d\'avoir une sauvegarde.';
String get deleteWalletWarning =>
'Toutes les données seront supprimées, y compris votre phrase de récupération et vos tokens. Assurez-vous d\'avoir une sauvegarde.';
@override
String get typeDeleteToConfirm => 'Tapez \"SUPPRIMER\" pour confirmer :';
@@ -694,13 +748,16 @@ class L10nFr extends L10n {
String get recoverTokensTitle => 'Récupérer les tokens';
@override
String get recoverTokensDescription => 'Scanner les mints pour récupérer les tokens associés à votre phrase de récupération (NUT-13)';
String get recoverTokensDescription =>
'Scanner les mints pour récupérer les tokens associés à votre phrase de récupération (NUT-13)';
@override
String get useCurrentSeedPhrase => 'Utiliser ma phrase de récupération actuelle';
String get useCurrentSeedPhrase =>
'Utiliser ma phrase de récupération actuelle';
@override
String get scanWithSavedWords => 'Scanner les mints avec les 12 mots sauvegardés';
String get scanWithSavedWords =>
'Scanner les mints avec les 12 mots sauvegardés';
@override
String get useOtherSeedPhrase => 'Utiliser une autre phrase de récupération';
@@ -720,7 +777,8 @@ class L10nFr extends L10n {
String get specificMint => 'Un mint spécifique';
@override
String get enterMnemonicWords => 'Entrez les 12 mots séparés par des espaces...';
String get enterMnemonicWords =>
'Entrez les 12 mots séparés par des espaces...';
@override
String get scanMints => 'Scanner les mints';
@@ -740,7 +798,8 @@ class L10nFr extends L10n {
}
@override
String get scanCompleteNoTokens => 'Scan terminé. Aucun nouveau token trouvé.';
String get scanCompleteNoTokens =>
'Scan terminé. Aucun nouveau token trouvé.';
@override
String mintsWithError(int count) {
@@ -763,7 +822,8 @@ class L10nFr extends L10n {
}
@override
String get noTokensForMnemonic => 'Aucun token trouvé associé à ce mnémonique.';
String get noTokensForMnemonic =>
'Aucun token trouvé associé à ce mnémonique.';
@override
String get noConnectedMints => 'Aucun mint connecté';
@@ -843,7 +903,8 @@ class L10nFr extends L10n {
String get deleteMintConfirmTitle => 'Supprimer le mint';
@override
String get deleteMintConfirmMessage => 'Si vous avez un solde sur ce mint, il sera perdu. Êtes-vous sûr ?';
String get deleteMintConfirmMessage =>
'Si vous avez un solde sur ce mint, il sera perdu. Êtes-vous sûr ?';
@override
String get delete => 'Supprimer';
@@ -854,6 +915,9 @@ class L10nFr extends L10n {
@override
String get selectAll => 'Tout';
@override
String get deselectAll => 'Aucun';
@override
String get selectNotesToSend => 'Sélectionnez les notes à envoyer :';
@@ -893,19 +957,24 @@ class L10nFr extends L10n {
String get tokenSavedForLater => 'Token sauvegardé pour réclamer plus tard';
@override
String get noConnectionTokenSaved => 'Pas de connexion. Token sauvegardé pour réclamer plus tard.';
String get noConnectionTokenSaved =>
'Pas de connexion. Token sauvegardé pour réclamer plus tard.';
@override
String get unknownMintOffline => 'Ce token provient d\'un mint inconnu. Connectez-vous à Internet pour l\'ajouter et réclamer le token.';
String get unknownMintOffline =>
'Ce token provient d\'un mint inconnu. Connectez-vous à Internet pour l\'ajouter et réclamer le token.';
@override
String get noConnectionTryLater => 'Pas de connexion au mint. Réessayez plus tard.';
String get noConnectionTryLater =>
'Pas de connexion au mint. Réessayez plus tard.';
@override
String get saveTokenError => 'Erreur lors de la sauvegarde du token. Veuillez réessayer.';
String get saveTokenError =>
'Erreur lors de la sauvegarde du token. Veuillez réessayer.';
@override
String get pendingTokenLimitReached => 'Limite de tokens en attente atteinte (max 50)';
String get pendingTokenLimitReached =>
'Limite de tokens en attente atteinte (max 50)';
@override
String get filterToReceive => 'À recevoir';
@@ -914,7 +983,8 @@ class L10nFr extends L10n {
String get noPendingTokens => 'Aucun token en attente';
@override
String get noPendingTokensHint => 'Sauvegardez des tokens pour les réclamer plus tard';
String get noPendingTokensHint =>
'Sauvegardez des tokens pour les réclamer plus tard';
@override
String get pendingBadge => 'EN ATTENTE';
@@ -973,19 +1043,23 @@ class L10nFr extends L10n {
String get pointCameraAtQr => 'Pointez la caméra vers le code QR';
@override
String get pointCameraAtCashuQr => 'Pointez la caméra vers le QR du token Cashu';
String get pointCameraAtCashuQr =>
'Pointez la caméra vers le QR du token Cashu';
@override
String get pointCameraAtInvoiceQr => 'Pointez la caméra vers le QR de la facture';
String get pointCameraAtInvoiceQr =>
'Pointez la caméra vers le QR de la facture';
@override
String get unrecognizedQrCode => 'Code QR non reconnu';
@override
String get scanCashuTokenHint => 'Scannez un token Cashu (cashuA... ou cashuB...)';
String get scanCashuTokenHint =>
'Scannez un token Cashu (cashuA... ou cashuB...)';
@override
String get scanLightningInvoiceHint => 'Scannez une facture Lightning (lnbc...)';
String get scanLightningInvoiceHint =>
'Scannez une facture Lightning (lnbc...)';
@override
String get addMintQuestion => 'Ajouter ce mint ?';
@@ -994,7 +1068,8 @@ class L10nFr extends L10n {
String get cameraPermissionDenied => 'Permission de la caméra refusée';
@override
String get paymentRequestNotSupported => 'Les demandes de paiement ne sont pas encore prises en charge';
String get paymentRequestNotSupported =>
'Les demandes de paiement ne sont pas encore prises en charge';
@override
String get p2pkTitle => 'Clés P2PK';
@@ -1003,10 +1078,12 @@ class L10nFr extends L10n {
String get p2pkSettingsDescription => 'Recevoir ecash verrouillé';
@override
String get p2pkExperimental => 'P2PK est expérimental. Utiliser avec prudence.';
String get p2pkExperimental =>
'P2PK est expérimental. Utiliser avec prudence.';
@override
String get p2pkPendingSendWarning => 'Vous avez un envoi P2PK en attente. Allez dans l\'historique et actualisez après que le destinataire a réclamé le jeton.';
String get p2pkPendingSendWarning =>
'Vous avez un envoi P2PK en attente. Allez dans l\'historique et actualisez après que le destinataire a réclamé le jeton.';
@override
String get p2pkExperimentalShort => 'Expérimental';
@@ -1063,7 +1140,8 @@ class L10nFr extends L10n {
String get p2pkLockedToOther => 'Verrouillé pour une autre clé';
@override
String get p2pkCannotUnlock => 'Vous n\'avez pas la clé pour déverrouiller ce token';
String get p2pkCannotUnlock =>
'Vous n\'avez pas la clé pour déverrouiller ce token';
@override
String get p2pkEnterPrivateKey => 'Entrer la clé privée (nsec)';
@@ -1072,13 +1150,15 @@ class L10nFr extends L10n {
String get p2pkDeleteTitle => 'Supprimer la clé';
@override
String get p2pkDeleteConfirm => 'Supprimer cette clé ? Vous ne pourrez plus recevoir de tokens verrouillés dessus.';
String get p2pkDeleteConfirm =>
'Supprimer cette clé ? Vous ne pourrez plus recevoir de tokens verrouillés dessus.';
@override
String get p2pkRequiresConnection => 'P2PK nécessite une connexion au mint';
@override
String get p2pkErrorMaxKeysReached => 'Nombre maximum de clés importées atteint (10)';
String get p2pkErrorMaxKeysReached =>
'Nombre maximum de clés importées atteint (10)';
@override
String get p2pkErrorInvalidNsec => 'nsec invalide';
@@ -1090,8 +1170,6 @@ class L10nFr extends L10n {
String get p2pkErrorKeyNotFound => 'Clé non trouvée';
@override
String get p2pkErrorCannotDeletePrimary => 'Impossible de supprimer la clé principale';
@override
String get p2pkSendComingSoon => 'Bientôt disponible';
String get p2pkErrorCannotDeletePrimary =>
'Impossible de supprimer la clé principale';
}
+98 -32
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -60,7 +60,8 @@ class L10nIt extends L10n {
String get generatingSeed => 'Generazione sicura della tua frase seed';
@override
String get createWalletDescription => 'Verrà generata una frase seed di 12 parole.\nConservala in un luogo sicuro.';
String get createWalletDescription =>
'Verrà generata una frase seed di 12 parole.\nConservala in un luogo sicuro.';
@override
String get generateWallet => 'Genera portafoglio';
@@ -69,10 +70,12 @@ class L10nIt extends L10n {
String get walletCreated => 'Portafoglio creato!';
@override
String get walletCreatedDescription => 'Il tuo portafoglio è pronto. Ti consigliamo di fare il backup della frase seed ora.';
String get walletCreatedDescription =>
'Il tuo portafoglio è pronto. Ti consigliamo di fare il backup della frase seed ora.';
@override
String get backupWarning => 'Senza backup, perderai l\'accesso ai tuoi fondi se perdi il dispositivo.';
String get backupWarning =>
'Senza backup, perderai l\'accesso ai tuoi fondi se perdi il dispositivo.';
@override
String get backupNow => 'Backup adesso';
@@ -87,7 +90,8 @@ class L10nIt extends L10n {
String get seedPhraseTitle => 'La tua frase seed';
@override
String get seedPhraseDescription => 'Conserva queste 12 parole in ordine. Sono l\'unico modo per recuperare il tuo portafoglio.';
String get seedPhraseDescription =>
'Conserva queste 12 parole in ordine. Sono l\'unico modo per recuperare il tuo portafoglio.';
@override
String get revealSeedPhrase => 'Mostra frase seed';
@@ -102,7 +106,8 @@ class L10nIt extends L10n {
String get seedCopied => 'Frase copiata negli appunti';
@override
String get neverShareSeed => 'Non condividere mai la tua frase seed con nessuno.';
String get neverShareSeed =>
'Non condividere mai la tua frase seed con nessuno.';
@override
String get confirmBackup => 'Ho salvato la mia frase seed in un luogo sicuro';
@@ -117,7 +122,8 @@ class L10nIt extends L10n {
String get enterSeedPhrase => 'Inserisci la tua frase seed';
@override
String get enterSeedDescription => 'Scrivi le 12 o 24 parole separate da spazi.';
String get enterSeedDescription =>
'Scrivi le 12 o 24 parole separate da spazi.';
@override
String get seedPlaceholder => 'parola1 parola2 parola3 ...';
@@ -310,11 +316,50 @@ class L10nIt extends L10n {
}
@override
String get keepTokenWarning => 'Conserva questo token finché il destinatario non lo riscatta. Se lo perdi, perderai i fondi.';
String get keepTokenWarning =>
'Conserva questo token finché il destinatario non lo riscatta. Se lo perdi, perderai i fondi.';
@override
String get tokenCopiedToClipboard => 'Token copiato negli appunti';
@override
String get copyAsEmoji => 'Copia come emoji';
@override
String get emojiCopiedToClipboard => 'Token copiato come emoji 🥜';
@override
String get peanutDecodeError =>
'Impossibile decodificare il token emoji. Potrebbe essere corrotto.';
@override
String get nfcWrite => 'Scrivi su tag NFC';
@override
String get nfcRead => 'Leggi tag NFC';
@override
String get nfcHoldNear => 'Avvicina il dispositivo al tag NFC...';
@override
String get nfcWriteSuccess => 'Token scritto sul tag NFC';
@override
String nfcWriteError(String error) {
return 'Errore NFC scrittura: $error';
}
@override
String nfcReadError(String error) {
return 'Errore NFC lettura: $error';
}
@override
String get nfcDisabled => 'NFC è disattivato. Attivalo nelle Impostazioni.';
@override
String get nfcUnsupported => 'Questo dispositivo non supporta NFC';
@override
String get amountToDeposit => 'Importo da depositare:';
@@ -450,7 +495,8 @@ class L10nIt extends L10n {
String get noPendingTransactions => 'Nessuna transazione in attesa';
@override
String get allTransactionsCompleted => 'Tutte le tue transazioni sono completate';
String get allTransactionsCompleted =>
'Tutte le tue transazioni sono completate';
@override
String get noEcashTransactions => 'Nessuna transazione Ecash';
@@ -665,7 +711,8 @@ class L10nIt extends L10n {
String get close => 'Chiudi';
@override
String get aboutDescription => 'Un portafoglio Cashu con DNA cubano per il mondo intero. Fratello di La Chispa.';
String get aboutDescription =>
'Un portafoglio Cashu con DNA cubano per il mondo intero. Fratello di LaChispa.';
@override
String get couldNotOpenLink => 'Impossibile aprire il link';
@@ -677,7 +724,8 @@ class L10nIt extends L10n {
String get actionIrreversible => 'Questa azione è irreversibile';
@override
String get deleteWalletWarning => 'Tutti i dati verranno eliminati, inclusa la frase seed e i token. Assicurati di avere un backup.';
String get deleteWalletWarning =>
'Tutti i dati verranno eliminati, inclusa la frase seed e i token. Assicurati di avere un backup.';
@override
String get typeDeleteToConfirm => 'Scrivi \"ELIMINA\" per confermare:';
@@ -694,7 +742,8 @@ class L10nIt extends L10n {
String get recoverTokensTitle => 'Recupera token';
@override
String get recoverTokensDescription => 'Scansiona i mint per recuperare i token associati alla tua frase seed (NUT-13)';
String get recoverTokensDescription =>
'Scansiona i mint per recuperare i token associati alla tua frase seed (NUT-13)';
@override
String get useCurrentSeedPhrase => 'Usa la mia frase seed attuale';
@@ -720,7 +769,8 @@ class L10nIt extends L10n {
String get specificMint => 'Un mint specifico';
@override
String get enterMnemonicWords => 'Inserisci le 12 parole separate da spazi...';
String get enterMnemonicWords =>
'Inserisci le 12 parole separate da spazi...';
@override
String get scanMints => 'Scansiona mint';
@@ -740,7 +790,8 @@ class L10nIt extends L10n {
}
@override
String get scanCompleteNoTokens => 'Scansione completata. Nessun nuovo token trovato.';
String get scanCompleteNoTokens =>
'Scansione completata. Nessun nuovo token trovato.';
@override
String mintsWithError(int count) {
@@ -763,7 +814,8 @@ class L10nIt extends L10n {
}
@override
String get noTokensForMnemonic => 'Nessun token trovato associato a questo mnemonic.';
String get noTokensForMnemonic =>
'Nessun token trovato associato a questo mnemonic.';
@override
String get noConnectedMints => 'Nessun mint connesso';
@@ -843,7 +895,8 @@ class L10nIt extends L10n {
String get deleteMintConfirmTitle => 'Elimina mint';
@override
String get deleteMintConfirmMessage => 'Se hai saldo su questo mint, verrà perso. Sei sicuro?';
String get deleteMintConfirmMessage =>
'Se hai saldo su questo mint, verrà perso. Sei sicuro?';
@override
String get delete => 'Elimina';
@@ -854,6 +907,9 @@ class L10nIt extends L10n {
@override
String get selectAll => 'Tutto';
@override
String get deselectAll => 'Nessuno';
@override
String get selectNotesToSend => 'Seleziona le note da inviare:';
@@ -893,19 +949,23 @@ class L10nIt extends L10n {
String get tokenSavedForLater => 'Token salvato per riscattarlo dopo';
@override
String get noConnectionTokenSaved => 'Nessuna connessione. Token salvato per riscattarlo dopo.';
String get noConnectionTokenSaved =>
'Nessuna connessione. Token salvato per riscattarlo dopo.';
@override
String get unknownMintOffline => 'Questo token proviene da un mint sconosciuto. Connettiti a internet per aggiungerlo e riscattare il token.';
String get unknownMintOffline =>
'Questo token proviene da un mint sconosciuto. Connettiti a internet per aggiungerlo e riscattare il token.';
@override
String get noConnectionTryLater => 'Nessuna connessione al mint. Riprova più tardi.';
String get noConnectionTryLater =>
'Nessuna connessione al mint. Riprova più tardi.';
@override
String get saveTokenError => 'Errore nel salvare il token. Riprova.';
@override
String get pendingTokenLimitReached => 'Limite token in attesa raggiunto (max 50)';
String get pendingTokenLimitReached =>
'Limite token in attesa raggiunto (max 50)';
@override
String get filterToReceive => 'Da ricevere';
@@ -973,19 +1033,23 @@ class L10nIt extends L10n {
String get pointCameraAtQr => 'Punta la fotocamera sul codice QR';
@override
String get pointCameraAtCashuQr => 'Punta la fotocamera sul QR del token Cashu';
String get pointCameraAtCashuQr =>
'Punta la fotocamera sul QR del token Cashu';
@override
String get pointCameraAtInvoiceQr => 'Punta la fotocamera sul QR della fattura';
String get pointCameraAtInvoiceQr =>
'Punta la fotocamera sul QR della fattura';
@override
String get unrecognizedQrCode => 'Codice QR non riconosciuto';
@override
String get scanCashuTokenHint => 'Scansiona un token Cashu (cashuA... o cashuB...)';
String get scanCashuTokenHint =>
'Scansiona un token Cashu (cashuA... o cashuB...)';
@override
String get scanLightningInvoiceHint => 'Scansiona una fattura Lightning (lnbc...)';
String get scanLightningInvoiceHint =>
'Scansiona una fattura Lightning (lnbc...)';
@override
String get addMintQuestion => 'Aggiungere questo mint?';
@@ -994,7 +1058,8 @@ class L10nIt extends L10n {
String get cameraPermissionDenied => 'Permesso fotocamera negato';
@override
String get paymentRequestNotSupported => 'Le richieste di pagamento non sono ancora supportate';
String get paymentRequestNotSupported =>
'Le richieste di pagamento non sono ancora supportate';
@override
String get p2pkTitle => 'Chiavi P2PK';
@@ -1006,7 +1071,8 @@ class L10nIt extends L10n {
String get p2pkExperimental => 'P2PK è sperimentale. Usare con cautela.';
@override
String get p2pkPendingSendWarning => 'Hai un invio P2PK in sospeso. Vai alla cronologia e aggiorna dopo che il destinatario ha riscattato il token.';
String get p2pkPendingSendWarning =>
'Hai un invio P2PK in sospeso. Vai alla cronologia e aggiorna dopo che il destinatario ha riscattato il token.';
@override
String get p2pkExperimentalShort => 'Sperimentale';
@@ -1072,13 +1138,15 @@ class L10nIt extends L10n {
String get p2pkDeleteTitle => 'Elimina chiave';
@override
String get p2pkDeleteConfirm => 'Eliminare questa chiave? Non potrai ricevere token bloccati ad essa.';
String get p2pkDeleteConfirm =>
'Eliminare questa chiave? Non potrai ricevere token bloccati ad essa.';
@override
String get p2pkRequiresConnection => 'P2PK richiede connessione al mint';
@override
String get p2pkErrorMaxKeysReached => 'Numero massimo di chiavi importate raggiunto (10)';
String get p2pkErrorMaxKeysReached =>
'Numero massimo di chiavi importate raggiunto (10)';
@override
String get p2pkErrorInvalidNsec => 'nsec non valido';
@@ -1090,8 +1158,6 @@ class L10nIt extends L10n {
String get p2pkErrorKeyNotFound => 'Chiave non trovata';
@override
String get p2pkErrorCannotDeletePrimary => 'Impossibile eliminare la chiave principale';
@override
String get p2pkSendComingSoon => 'Disponibile prossimamente';
String get p2pkErrorCannotDeletePrimary =>
'Impossibile eliminare la chiave principale';
}
+52 -10
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -69,7 +69,8 @@ class L10nJa extends L10n {
String get walletCreated => 'ウォレット作成完了!';
@override
String get walletCreatedDescription => 'ウォレットの準備ができました。今すぐシードフレーズをバックアップすることをお勧めします。';
String get walletCreatedDescription =>
'ウォレットの準備ができました。今すぐシードフレーズをバックアップすることをお勧めします。';
@override
String get backupWarning => 'バックアップがないと、デバイスを紛失した場合に資金にアクセスできなくなります。';
@@ -315,6 +316,43 @@ class L10nJa extends L10n {
@override
String get tokenCopiedToClipboard => 'トークンをクリップボードにコピーしました';
@override
String get copyAsEmoji => '絵文字としてコピー';
@override
String get emojiCopiedToClipboard => 'トークンを絵文字としてコピーしました 🥜';
@override
String get peanutDecodeError => '絵文字トークンをデコードできませんでした。破損している可能性があります。';
@override
String get nfcWrite => 'NFCタグに書き込む';
@override
String get nfcRead => 'NFCタグを読み取る';
@override
String get nfcHoldNear => 'デバイスをNFCタグに近づけてください...';
@override
String get nfcWriteSuccess => 'トークンをNFCタグに書き込みました';
@override
String nfcWriteError(String error) {
return 'NFC書き込みエラー: $error';
}
@override
String nfcReadError(String error) {
return 'NFC読み取りエラー: $error';
}
@override
String get nfcDisabled => 'NFCが無効です。設定で有効にしてください。';
@override
String get nfcUnsupported => 'このデバイスはNFCに対応していません';
@override
String get amountToDeposit => '入金額:';
@@ -665,7 +703,7 @@ class L10nJa extends L10n {
String get close => '閉じる';
@override
String get aboutDescription => 'キューバのDNAを持つ世界のためのCashuウォレット。La Chispaの兄弟。';
String get aboutDescription => 'キューバのDNAを持つ世界のためのCashuウォレット。LaChispaの兄弟。';
@override
String get couldNotOpenLink => 'リンクを開けませんでした';
@@ -677,7 +715,8 @@ class L10nJa extends L10n {
String get actionIrreversible => 'この操作は取り消せません';
@override
String get deleteWalletWarning => 'シードフレーズとトークンを含むすべてのデータが削除されます。バックアップがあることを確認してください。';
String get deleteWalletWarning =>
'シードフレーズとトークンを含むすべてのデータが削除されます。バックアップがあることを確認してください。';
@override
String get typeDeleteToConfirm => '確認のため「DELETE」と入力:';
@@ -694,7 +733,8 @@ class L10nJa extends L10n {
String get recoverTokensTitle => 'トークンを復元';
@override
String get recoverTokensDescription => 'シードフレーズに関連付けられたトークンを復元するためにMintをスキャン(NUT-13)';
String get recoverTokensDescription =>
'シードフレーズに関連付けられたトークンを復元するためにMintをスキャン(NUT-13)';
@override
String get useCurrentSeedPhrase => '現在のシードフレーズを使用';
@@ -854,6 +894,9 @@ class L10nJa extends L10n {
@override
String get selectAll => 'すべて';
@override
String get deselectAll => 'なし';
@override
String get selectNotesToSend => '送信するノートを選択:';
@@ -896,7 +939,8 @@ class L10nJa extends L10n {
String get noConnectionTokenSaved => '接続なし。トークンを保存しました。後で請求できます。';
@override
String get unknownMintOffline => 'このトークンは不明なMintからのものです。インターネットに接続してMintを追加し、トークンを請求してください。';
String get unknownMintOffline =>
'このトークンは不明なMintからのものです。インターネットに接続してMintを追加し、トークンを請求してください。';
@override
String get noConnectionTryLater => 'Mintに接続できません。後でもう一度お試しください。';
@@ -1006,7 +1050,8 @@ class L10nJa extends L10n {
String get p2pkExperimental => 'P2PKは実験的機能です。注意してご使用ください。';
@override
String get p2pkPendingSendWarning => '保留中のP2PK送信があります。受取人がトークンを受け取った後、履歴で更新してください。';
String get p2pkPendingSendWarning =>
'保留中のP2PK送信があります。受取人がトークンを受け取った後、履歴で更新してください。';
@override
String get p2pkExperimentalShort => '実験的';
@@ -1091,7 +1136,4 @@ class L10nJa extends L10n {
@override
String get p2pkErrorCannotDeletePrimary => 'プライマリ鍵は削除できません';
@override
String get p2pkSendComingSoon => '近日公開';
}
+56 -12
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -60,7 +60,8 @@ class L10nKo extends L10n {
String get generatingSeed => '시드 문구를 안전하게 생성 중';
@override
String get createWalletDescription => '12개의 단어로 된 시드 문구가 생성됩니다.\n안전한 곳에 보관하세요.';
String get createWalletDescription =>
'12개의 단어로 된 시드 문구가 생성됩니다.\n안전한 곳에 보관하세요.';
@override
String get generateWallet => '지갑 생성';
@@ -69,7 +70,8 @@ class L10nKo extends L10n {
String get walletCreated => '지갑이 생성되었습니다!';
@override
String get walletCreatedDescription => '지갑이 준비되었습니다. 지금 시드 문구를 백업하는 것을 권장합니다.';
String get walletCreatedDescription =>
'지갑이 준비되었습니다. 지금 시드 문구를 백업하는 것을 권장합니다.';
@override
String get backupWarning => '백업 없이는 기기를 분실하면 자금에 접근할 수 없습니다.';
@@ -87,7 +89,8 @@ class L10nKo extends L10n {
String get seedPhraseTitle => '시드 문구';
@override
String get seedPhraseDescription => '이 12개의 단어를 순서대로 저장하세요. 지갑을 복구하는 유일한 방법입니다.';
String get seedPhraseDescription =>
'이 12개의 단어를 순서대로 저장하세요. 지갑을 복구하는 유일한 방법입니다.';
@override
String get revealSeedPhrase => '시드 문구 보기';
@@ -315,6 +318,43 @@ class L10nKo extends L10n {
@override
String get tokenCopiedToClipboard => '토큰이 클립보드에 복사되었습니다';
@override
String get copyAsEmoji => '이모지로 복사';
@override
String get emojiCopiedToClipboard => '토큰이 이모지로 복사되었습니다 🥜';
@override
String get peanutDecodeError => '이모지 토큰을 디코딩할 수 없습니다. 손상되었을 수 있습니다.';
@override
String get nfcWrite => 'NFC 태그에 쓰기';
@override
String get nfcRead => 'NFC 태그 읽기';
@override
String get nfcHoldNear => '기기를 NFC 태그에 가까이 대세요...';
@override
String get nfcWriteSuccess => '토큰이 NFC 태그에 기록되었습니다';
@override
String nfcWriteError(String error) {
return 'NFC 쓰기 오류: $error';
}
@override
String nfcReadError(String error) {
return 'NFC 읽기 오류: $error';
}
@override
String get nfcDisabled => 'NFC가 비활성화되어 있습니다. 설정에서 활성화하세요.';
@override
String get nfcUnsupported => '이 기기는 NFC를 지원하지 않습니다';
@override
String get amountToDeposit => '입금할 금액:';
@@ -665,7 +705,7 @@ class L10nKo extends L10n {
String get close => '닫기';
@override
String get aboutDescription => '전 세계를 위한 쿠바 DNA를 가진 Cashu 지갑. La Chispa의 형제.';
String get aboutDescription => '전 세계를 위한 쿠바 DNA를 가진 Cashu 지갑. LaChispa의 형제.';
@override
String get couldNotOpenLink => '링크를 열 수 없음';
@@ -677,7 +717,8 @@ class L10nKo extends L10n {
String get actionIrreversible => '이 작업은 되돌릴 수 없습니다';
@override
String get deleteWalletWarning => '시드 문구와 토큰을 포함한 모든 데이터가 삭제됩니다. 백업이 있는지 확인하세요.';
String get deleteWalletWarning =>
'시드 문구와 토큰을 포함한 모든 데이터가 삭제됩니다. 백업이 있는지 확인하세요.';
@override
String get typeDeleteToConfirm => '확인하려면 \"삭제\"를 입력하세요:';
@@ -694,7 +735,8 @@ class L10nKo extends L10n {
String get recoverTokensTitle => '토큰 복구';
@override
String get recoverTokensDescription => '시드 문구와 연결된 토큰을 복구하기 위해 mint 스캔 (NUT-13)';
String get recoverTokensDescription =>
'시드 문구와 연결된 토큰을 복구하기 위해 mint 스캔 (NUT-13)';
@override
String get useCurrentSeedPhrase => '현재 시드 문구 사용';
@@ -854,6 +896,9 @@ class L10nKo extends L10n {
@override
String get selectAll => '전체';
@override
String get deselectAll => '없음';
@override
String get selectNotesToSend => '보낼 노트를 선택하세요:';
@@ -896,7 +941,8 @@ class L10nKo extends L10n {
String get noConnectionTokenSaved => '연결 없음. 나중에 청구하기 위해 토큰이 저장되었습니다.';
@override
String get unknownMintOffline => '이 토큰은 알 수 없는 mint의 것입니다. 인터넷에 연결하여 추가하고 토큰을 청구하세요.';
String get unknownMintOffline =>
'이 토큰은 알 수 없는 mint의 것입니다. 인터넷에 연결하여 추가하고 토큰을 청구하세요.';
@override
String get noConnectionTryLater => 'Mint에 연결할 수 없음. 나중에 다시 시도하세요.';
@@ -1006,7 +1052,8 @@ class L10nKo extends L10n {
String get p2pkExperimental => 'P2PK는 실험적 기능입니다. 주의하여 사용하세요.';
@override
String get p2pkPendingSendWarning => '대기 중인 P2PK 전송이 있습니다. 수신자가 토큰을 수령한 후 기록에서 새로고침하세요.';
String get p2pkPendingSendWarning =>
'대기 중인 P2PK 전송이 있습니다. 수신자가 토큰을 수령한 후 기록에서 새로고침하세요.';
@override
String get p2pkExperimentalShort => '실험적';
@@ -1091,7 +1138,4 @@ class L10nKo extends L10n {
@override
String get p2pkErrorCannotDeletePrimary => '기본 키는 삭제할 수 없습니다';
@override
String get p2pkSendComingSoon => '곧 출시 예정';
}
+98 -32
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -60,7 +60,8 @@ class L10nPt extends L10n {
String get generatingSeed => 'Gerando sua frase semente de forma segura';
@override
String get createWalletDescription => 'Uma frase semente de 12 palavras será gerada.\nGuarde-a em um lugar seguro.';
String get createWalletDescription =>
'Uma frase semente de 12 palavras será gerada.\nGuarde-a em um lugar seguro.';
@override
String get generateWallet => 'Gerar wallet';
@@ -69,10 +70,12 @@ class L10nPt extends L10n {
String get walletCreated => 'Wallet criada!';
@override
String get walletCreatedDescription => 'Sua wallet está pronta. Recomendamos fazer backup da sua frase semente agora.';
String get walletCreatedDescription =>
'Sua wallet está pronta. Recomendamos fazer backup da sua frase semente agora.';
@override
String get backupWarning => 'Sem backup, você perderá acesso aos seus fundos se perder o dispositivo.';
String get backupWarning =>
'Sem backup, você perderá acesso aos seus fundos se perder o dispositivo.';
@override
String get backupNow => 'Fazer backup agora';
@@ -87,7 +90,8 @@ class L10nPt extends L10n {
String get seedPhraseTitle => 'Sua frase semente';
@override
String get seedPhraseDescription => 'Guarde estas 12 palavras em ordem. Elas são a única forma de recuperar sua wallet.';
String get seedPhraseDescription =>
'Guarde estas 12 palavras em ordem. Elas são a única forma de recuperar sua wallet.';
@override
String get revealSeedPhrase => 'Revelar frase semente';
@@ -102,7 +106,8 @@ class L10nPt extends L10n {
String get seedCopied => 'Frase copiada para área de transferência';
@override
String get neverShareSeed => 'Nunca compartilhe sua frase semente com ninguém.';
String get neverShareSeed =>
'Nunca compartilhe sua frase semente com ninguém.';
@override
String get confirmBackup => 'Guardei minha frase semente em um lugar seguro';
@@ -117,7 +122,8 @@ class L10nPt extends L10n {
String get enterSeedPhrase => 'Digite sua frase semente';
@override
String get enterSeedDescription => 'Digite as 12 ou 24 palavras separadas por espaços.';
String get enterSeedDescription =>
'Digite as 12 ou 24 palavras separadas por espaços.';
@override
String get seedPlaceholder => 'palavra1 palavra2 palavra3 ...';
@@ -310,10 +316,50 @@ class L10nPt extends L10n {
}
@override
String get keepTokenWarning => 'Guarde este token até que o destinatário o resgate. Se você perdê-lo, perderá os fundos.';
String get keepTokenWarning =>
'Guarde este token até que o destinatário o resgate. Se você perdê-lo, perderá os fundos.';
@override
String get tokenCopiedToClipboard => 'Token copiado para área de transferência';
String get tokenCopiedToClipboard =>
'Token copiado para área de transferência';
@override
String get copyAsEmoji => 'Copiar como emoji';
@override
String get emojiCopiedToClipboard => 'Token copiado como emoji 🥜';
@override
String get peanutDecodeError =>
'Não foi possível decodificar o token emoji. Pode estar corrompido.';
@override
String get nfcWrite => 'Escrever em tag NFC';
@override
String get nfcRead => 'Ler tag NFC';
@override
String get nfcHoldNear => 'Aproxime o dispositivo da tag NFC...';
@override
String get nfcWriteSuccess => 'Token escrito na tag NFC';
@override
String nfcWriteError(String error) {
return 'Erro NFC ao escrever: $error';
}
@override
String nfcReadError(String error) {
return 'Erro NFC ao ler: $error';
}
@override
String get nfcDisabled => 'NFC está desativado. Ative nas Configurações.';
@override
String get nfcUnsupported => 'Este dispositivo não suporta NFC';
@override
String get amountToDeposit => 'Valor a depositar:';
@@ -364,7 +410,8 @@ class L10nPt extends L10n {
String get description => 'Descrição:';
@override
String get invoiceCopiedToClipboard => 'Invoice copiado para área de transferência';
String get invoiceCopiedToClipboard =>
'Invoice copiado para área de transferência';
@override
String deposited(String amount, String unit) {
@@ -450,7 +497,8 @@ class L10nPt extends L10n {
String get noPendingTransactions => 'Sem transações pendentes';
@override
String get allTransactionsCompleted => 'Todas as suas transações estão completas';
String get allTransactionsCompleted =>
'Todas as suas transações estão completas';
@override
String get noEcashTransactions => 'Sem transações Ecash';
@@ -665,7 +713,8 @@ class L10nPt extends L10n {
String get close => 'Fechar';
@override
String get aboutDescription => 'Uma wallet Cashu com DNA cubano para o mundo inteiro. Irmã de La Chispa.';
String get aboutDescription =>
'Uma wallet Cashu com DNA cubano para o mundo inteiro. Irmã de LaChispa.';
@override
String get couldNotOpenLink => 'Não foi possível abrir o link';
@@ -677,7 +726,8 @@ class L10nPt extends L10n {
String get actionIrreversible => 'Esta ação é irreversível';
@override
String get deleteWalletWarning => 'Todos os dados serão excluídos, incluindo sua frase semente e tokens. Certifique-se de ter um backup.';
String get deleteWalletWarning =>
'Todos os dados serão excluídos, incluindo sua frase semente e tokens. Certifique-se de ter um backup.';
@override
String get typeDeleteToConfirm => 'Digite \"APAGAR\" para confirmar:';
@@ -694,7 +744,8 @@ class L10nPt extends L10n {
String get recoverTokensTitle => 'Recuperar tokens';
@override
String get recoverTokensDescription => 'Escanear mints para recuperar tokens associados à sua frase semente (NUT-13)';
String get recoverTokensDescription =>
'Escanear mints para recuperar tokens associados à sua frase semente (NUT-13)';
@override
String get useCurrentSeedPhrase => 'Usar minha frase semente atual';
@@ -720,7 +771,8 @@ class L10nPt extends L10n {
String get specificMint => 'Um mint específico';
@override
String get enterMnemonicWords => 'Digite as 12 palavras separadas por espaços...';
String get enterMnemonicWords =>
'Digite as 12 palavras separadas por espaços...';
@override
String get scanMints => 'Escanear mints';
@@ -740,7 +792,8 @@ class L10nPt extends L10n {
}
@override
String get scanCompleteNoTokens => 'Escaneamento completo. Nenhum token novo encontrado.';
String get scanCompleteNoTokens =>
'Escaneamento completo. Nenhum token novo encontrado.';
@override
String mintsWithError(int count) {
@@ -763,7 +816,8 @@ class L10nPt extends L10n {
}
@override
String get noTokensForMnemonic => 'Nenhum token encontrado associado a esse mnemônico.';
String get noTokensForMnemonic =>
'Nenhum token encontrado associado a esse mnemônico.';
@override
String get noConnectedMints => 'Nenhum mint conectado';
@@ -843,7 +897,8 @@ class L10nPt extends L10n {
String get deleteMintConfirmTitle => 'Excluir mint';
@override
String get deleteMintConfirmMessage => 'Se você tiver saldo neste mint, ele será perdido. Tem certeza?';
String get deleteMintConfirmMessage =>
'Se você tiver saldo neste mint, ele será perdido. Tem certeza?';
@override
String get delete => 'Excluir';
@@ -854,6 +909,9 @@ class L10nPt extends L10n {
@override
String get selectAll => 'Tudo';
@override
String get deselectAll => 'Nenhum';
@override
String get selectNotesToSend => 'Selecione as notas que deseja enviar:';
@@ -893,10 +951,12 @@ class L10nPt extends L10n {
String get tokenSavedForLater => 'Token salvo para resgatar depois';
@override
String get noConnectionTokenSaved => 'Sem conexão. Token salvo para resgatar depois.';
String get noConnectionTokenSaved =>
'Sem conexão. Token salvo para resgatar depois.';
@override
String get unknownMintOffline => 'Este token é de um mint desconhecido. Conecte-se à internet para adicioná-lo e resgatar o token.';
String get unknownMintOffline =>
'Este token é de um mint desconhecido. Conecte-se à internet para adicioná-lo e resgatar o token.';
@override
String get noConnectionTryLater => 'Sem conexão ao mint. Tente mais tarde.';
@@ -905,7 +965,8 @@ class L10nPt extends L10n {
String get saveTokenError => 'Erro ao salvar o token. Tente novamente.';
@override
String get pendingTokenLimitReached => 'Limite de tokens pendentes atingido (máx 50)';
String get pendingTokenLimitReached =>
'Limite de tokens pendentes atingido (máx 50)';
@override
String get filterToReceive => 'Para receber';
@@ -982,10 +1043,12 @@ class L10nPt extends L10n {
String get unrecognizedQrCode => 'Código QR não reconhecido';
@override
String get scanCashuTokenHint => 'Escaneie um token Cashu (cashuA... ou cashuB...)';
String get scanCashuTokenHint =>
'Escaneie um token Cashu (cashuA... ou cashuB...)';
@override
String get scanLightningInvoiceHint => 'Escaneie um invoice Lightning (lnbc...)';
String get scanLightningInvoiceHint =>
'Escaneie um invoice Lightning (lnbc...)';
@override
String get addMintQuestion => 'Adicionar este mint?';
@@ -994,7 +1057,8 @@ class L10nPt extends L10n {
String get cameraPermissionDenied => 'Permissão de câmera negada';
@override
String get paymentRequestNotSupported => 'Solicitações de pagamento ainda não são suportadas';
String get paymentRequestNotSupported =>
'Solicitações de pagamento ainda não são suportadas';
@override
String get p2pkTitle => 'Chaves P2PK';
@@ -1006,7 +1070,8 @@ class L10nPt extends L10n {
String get p2pkExperimental => 'P2PK é experimental. Use com cautela.';
@override
String get p2pkPendingSendWarning => 'Você tem um envio P2PK pendente. Vá ao histórico e atualize após o destinatário resgatar o token.';
String get p2pkPendingSendWarning =>
'Você tem um envio P2PK pendente. Vá ao histórico e atualize após o destinatário resgatar o token.';
@override
String get p2pkExperimentalShort => 'Experimental';
@@ -1063,7 +1128,8 @@ class L10nPt extends L10n {
String get p2pkLockedToOther => 'Bloqueado para outra chave';
@override
String get p2pkCannotUnlock => 'Você não tem a chave para desbloquear este token';
String get p2pkCannotUnlock =>
'Você não tem a chave para desbloquear este token';
@override
String get p2pkEnterPrivateKey => 'Digite a chave privada (nsec)';
@@ -1072,13 +1138,15 @@ class L10nPt extends L10n {
String get p2pkDeleteTitle => 'Excluir chave';
@override
String get p2pkDeleteConfirm => 'Excluir esta chave? Você não poderá receber tokens bloqueados para ela.';
String get p2pkDeleteConfirm =>
'Excluir esta chave? Você não poderá receber tokens bloqueados para ela.';
@override
String get p2pkRequiresConnection => 'P2PK requer conexão com o mint';
@override
String get p2pkErrorMaxKeysReached => 'Número máximo de chaves importadas atingido (10)';
String get p2pkErrorMaxKeysReached =>
'Número máximo de chaves importadas atingido (10)';
@override
String get p2pkErrorInvalidNsec => 'nsec inválido';
@@ -1090,8 +1158,6 @@ class L10nPt extends L10n {
String get p2pkErrorKeyNotFound => 'Chave não encontrada';
@override
String get p2pkErrorCannotDeletePrimary => 'Não é possível excluir a chave principal';
@override
String get p2pkSendComingSoon => 'Em breve disponível';
String get p2pkErrorCannotDeletePrimary =>
'Não é possível excluir a chave principal';
}
+92 -29
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -33,7 +33,8 @@ class L10nRu extends L10n {
String get loadingMessage6 => 'Go full Calle...';
@override
String get loadingMessage7 => 'Cashu + Bitchat = Конфиденциальность + Свобода';
String get loadingMessage7 =>
'Cashu + Bitchat = Конфиденциальность + Свобода';
@override
String get aboutTagline => 'Конфиденциальность без границ.';
@@ -60,7 +61,8 @@ class L10nRu extends L10n {
String get generatingSeed => 'Безопасная генерация вашей сид-фразы';
@override
String get createWalletDescription => 'Будет сгенерирована сид-фраза из 12 слов.\nСохраните её в безопасном месте.';
String get createWalletDescription =>
'Будет сгенерирована сид-фраза из 12 слов.\nСохраните её в безопасном месте.';
@override
String get generateWallet => 'Создать кошелёк';
@@ -69,10 +71,12 @@ class L10nRu extends L10n {
String get walletCreated => 'Кошелёк создан!';
@override
String get walletCreatedDescription => 'Ваш кошелёк готов. Рекомендуем сделать резервную копию сид-фразы сейчас.';
String get walletCreatedDescription =>
'Ваш кошелёк готов. Рекомендуем сделать резервную копию сид-фразы сейчас.';
@override
String get backupWarning => 'Без резервной копии вы потеряете доступ к средствам при потере устройства.';
String get backupWarning =>
'Без резервной копии вы потеряете доступ к средствам при потере устройства.';
@override
String get backupNow => 'Сделать резервную копию';
@@ -87,7 +91,8 @@ class L10nRu extends L10n {
String get seedPhraseTitle => 'Ваша сид-фраза';
@override
String get seedPhraseDescription => 'Сохраните эти 12 слов по порядку. Это единственный способ восстановить кошелёк.';
String get seedPhraseDescription =>
'Сохраните эти 12 слов по порядку. Это единственный способ восстановить кошелёк.';
@override
String get revealSeedPhrase => 'Показать сид-фразу';
@@ -310,11 +315,50 @@ class L10nRu extends L10n {
}
@override
String get keepTokenWarning => 'Сохраните этот токен, пока получатель не заберёт его. Если потеряете — потеряете средства.';
String get keepTokenWarning =>
'Сохраните этот токен, пока получатель не заберёт его. Если потеряете — потеряете средства.';
@override
String get tokenCopiedToClipboard => 'Токен скопирован в буфер обмена';
@override
String get copyAsEmoji => 'Скопировать как эмодзи';
@override
String get emojiCopiedToClipboard => 'Токен скопирован как эмодзи 🥜';
@override
String get peanutDecodeError =>
'Не удалось декодировать эмодзи-токен. Возможно, он повреждён.';
@override
String get nfcWrite => 'Записать на NFC-метку';
@override
String get nfcRead => 'Читать NFC-метку';
@override
String get nfcHoldNear => 'Поднесите устройство к NFC-метке...';
@override
String get nfcWriteSuccess => 'Токен записан на NFC-метку';
@override
String nfcWriteError(String error) {
return 'Ошибка записи NFC: $error';
}
@override
String nfcReadError(String error) {
return 'Ошибка чтения NFC: $error';
}
@override
String get nfcDisabled => 'NFC отключён. Включите в Настройках.';
@override
String get nfcUnsupported => 'Это устройство не поддерживает NFC';
@override
String get amountToDeposit => 'Сумма для пополнения:';
@@ -384,7 +428,8 @@ class L10nRu extends L10n {
String get invalidInvoice => 'Недействительный счёт';
@override
String get invalidInvoiceMalformed => 'Недействительный или повреждённый счёт';
String get invalidInvoiceMalformed =>
'Недействительный или повреждённый счёт';
@override
String get feeReserved => 'Зарезервированная комиссия:';
@@ -462,7 +507,8 @@ class L10nRu extends L10n {
String get noLightningTransactions => 'Нет Lightning транзакций';
@override
String get depositOrWithdrawLightning => 'Пополните или выведите через Lightning';
String get depositOrWithdrawLightning =>
'Пополните или выведите через Lightning';
@override
String get pendingStatus => 'Ожидание';
@@ -665,7 +711,8 @@ class L10nRu extends L10n {
String get close => 'Закрыть';
@override
String get aboutDescription => 'Cashu кошелёк с кубинской ДНК для всего мира. Брат La Chispa.';
String get aboutDescription =>
'Cashu кошелёк с кубинской ДНК для всего мира. Брат LaChispa.';
@override
String get couldNotOpenLink => 'Не удалось открыть ссылку';
@@ -677,7 +724,8 @@ class L10nRu extends L10n {
String get actionIrreversible => 'Это действие необратимо';
@override
String get deleteWalletWarning => 'Все данные будут удалены, включая сид-фразу и токены. Убедитесь, что у вас есть резервная копия.';
String get deleteWalletWarning =>
'Все данные будут удалены, включая сид-фразу и токены. Убедитесь, что у вас есть резервная копия.';
@override
String get typeDeleteToConfirm => 'Введите \"УДАЛИТЬ\" для подтверждения:';
@@ -694,7 +742,8 @@ class L10nRu extends L10n {
String get recoverTokensTitle => 'Восстановить токены';
@override
String get recoverTokensDescription => 'Сканировать mint для восстановления токенов, связанных с вашей сид-фразой (NUT-13)';
String get recoverTokensDescription =>
'Сканировать mint для восстановления токенов, связанных с вашей сид-фразой (NUT-13)';
@override
String get useCurrentSeedPhrase => 'Использовать текущую сид-фразу';
@@ -729,7 +778,8 @@ class L10nRu extends L10n {
String get selectMintToScan => 'Выберите mint для сканирования';
@override
String get mnemonicMustHaveWords => 'Мнемоника должна содержать 12 или 24 слова';
String get mnemonicMustHaveWords =>
'Мнемоника должна содержать 12 или 24 слова';
@override
String get noConnectedMintsToScan => 'Нет подключённых mint для сканирования';
@@ -740,7 +790,8 @@ class L10nRu extends L10n {
}
@override
String get scanCompleteNoTokens => 'Сканирование завершено. Новых токенов не найдено.';
String get scanCompleteNoTokens =>
'Сканирование завершено. Новых токенов не найдено.';
@override
String mintsWithError(int count) {
@@ -763,7 +814,8 @@ class L10nRu extends L10n {
}
@override
String get noTokensForMnemonic => 'Токены, связанные с этой мнемоникой, не найдены.';
String get noTokensForMnemonic =>
'Токены, связанные с этой мнемоникой, не найдены.';
@override
String get noConnectedMints => 'Нет подключённых mint';
@@ -843,7 +895,8 @@ class L10nRu extends L10n {
String get deleteMintConfirmTitle => 'Удалить mint';
@override
String get deleteMintConfirmMessage => 'Если у вас есть баланс на этом mint, он будет потерян. Вы уверены?';
String get deleteMintConfirmMessage =>
'Если у вас есть баланс на этом mint, он будет потерян. Вы уверены?';
@override
String get delete => 'Удалить';
@@ -854,6 +907,9 @@ class L10nRu extends L10n {
@override
String get selectAll => 'Все';
@override
String get deselectAll => 'Ничего';
@override
String get selectNotesToSend => 'Выберите заметки для отправки:';
@@ -893,10 +949,12 @@ class L10nRu extends L10n {
String get tokenSavedForLater => 'Токен сохранён для получения позже';
@override
String get noConnectionTokenSaved => 'Нет соединения. Токен сохранён для получения позже.';
String get noConnectionTokenSaved =>
'Нет соединения. Токен сохранён для получения позже.';
@override
String get unknownMintOffline => 'Этот токен от неизвестного mint. Подключитесь к интернету, чтобы добавить его и получить токен.';
String get unknownMintOffline =>
'Этот токен от неизвестного mint. Подключитесь к интернету, чтобы добавить его и получить токен.';
@override
String get noConnectionTryLater => 'Нет соединения с mint. Попробуйте позже.';
@@ -905,7 +963,8 @@ class L10nRu extends L10n {
String get saveTokenError => 'Ошибка сохранения токена. Попробуйте снова.';
@override
String get pendingTokenLimitReached => 'Достигнут лимит ожидающих токенов (макс 50)';
String get pendingTokenLimitReached =>
'Достигнут лимит ожидающих токенов (макс 50)';
@override
String get filterToReceive => 'К получению';
@@ -982,7 +1041,8 @@ class L10nRu extends L10n {
String get unrecognizedQrCode => 'Нераспознанный QR-код';
@override
String get scanCashuTokenHint => 'Сканируйте Cashu токен (cashuA... или cashuB...)';
String get scanCashuTokenHint =>
'Сканируйте Cashu токен (cashuA... или cashuB...)';
@override
String get scanLightningInvoiceHint => 'Сканируйте Lightning счёт (lnbc...)';
@@ -994,7 +1054,8 @@ class L10nRu extends L10n {
String get cameraPermissionDenied => 'Доступ к камере запрещён';
@override
String get paymentRequestNotSupported => 'Запросы на оплату пока не поддерживаются';
String get paymentRequestNotSupported =>
'Запросы на оплату пока не поддерживаются';
@override
String get p2pkTitle => 'Ключи P2PK';
@@ -1003,10 +1064,12 @@ class L10nRu extends L10n {
String get p2pkSettingsDescription => 'Получить заблокированный ecash';
@override
String get p2pkExperimental => 'P2PK экспериментальный. Используйте с осторожностью.';
String get p2pkExperimental =>
'P2PK экспериментальный. Используйте с осторожностью.';
@override
String get p2pkPendingSendWarning => 'У вас есть ожидающая отправка P2PK. Перейдите в историю и обновите после того, как получатель заберёт токен.';
String get p2pkPendingSendWarning =>
'У вас есть ожидающая отправка P2PK. Перейдите в историю и обновите после того, как получатель заберёт токен.';
@override
String get p2pkExperimentalShort => 'Экспериментальный';
@@ -1063,7 +1126,8 @@ class L10nRu extends L10n {
String get p2pkLockedToOther => 'Заблокировано для другого ключа';
@override
String get p2pkCannotUnlock => 'У вас нет ключа для разблокировки этого токена';
String get p2pkCannotUnlock =>
'У вас нет ключа для разблокировки этого токена';
@override
String get p2pkEnterPrivateKey => 'Введите приватный ключ (nsec)';
@@ -1072,13 +1136,15 @@ class L10nRu extends L10n {
String get p2pkDeleteTitle => 'Удалить ключ';
@override
String get p2pkDeleteConfirm => 'Удалить этот ключ? Вы не сможете получать токены заблокированные на него.';
String get p2pkDeleteConfirm =>
'Удалить этот ключ? Вы не сможете получать токены заблокированные на него.';
@override
String get p2pkRequiresConnection => 'P2PK требует подключения к mint';
@override
String get p2pkErrorMaxKeysReached => 'Достигнуто максимальное количество импортированных ключей (10)';
String get p2pkErrorMaxKeysReached =>
'Достигнуто максимальное количество импортированных ключей (10)';
@override
String get p2pkErrorInvalidNsec => 'Недействительный nsec';
@@ -1091,7 +1157,4 @@ class L10nRu extends L10n {
@override
String get p2pkErrorCannotDeletePrimary => 'Невозможно удалить основной ключ';
@override
String get p2pkSendComingSoon => 'Скоро будет доступно';
}
+92 -29
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -60,7 +60,8 @@ class L10nSw extends L10n {
String get generatingSeed => 'Kutengeneza maneno yako ya mbegu kwa usalama';
@override
String get createWalletDescription => 'Maneno 12 ya mbegu yatatengenezwa.\nYahifadhi mahali salama.';
String get createWalletDescription =>
'Maneno 12 ya mbegu yatatengenezwa.\nYahifadhi mahali salama.';
@override
String get generateWallet => 'Tengeneza pochi';
@@ -69,10 +70,12 @@ class L10nSw extends L10n {
String get walletCreated => 'Pochi imeundwa!';
@override
String get walletCreatedDescription => 'Pochi yako iko tayari. Tunakushauri uhifadhi nakala ya maneno yako ya mbegu sasa.';
String get walletCreatedDescription =>
'Pochi yako iko tayari. Tunakushauri uhifadhi nakala ya maneno yako ya mbegu sasa.';
@override
String get backupWarning => 'Bila nakala, utapoteza uwezo wa kufikia fedha zako ukipoteza kifaa.';
String get backupWarning =>
'Bila nakala, utapoteza uwezo wa kufikia fedha zako ukipoteza kifaa.';
@override
String get backupNow => 'Hifadhi nakala sasa';
@@ -87,7 +90,8 @@ class L10nSw extends L10n {
String get seedPhraseTitle => 'Maneno yako ya mbegu';
@override
String get seedPhraseDescription => 'Hifadhi maneno haya 12 kwa mpangilio. Ndiyo njia pekee ya kurejesha pochi yako.';
String get seedPhraseDescription =>
'Hifadhi maneno haya 12 kwa mpangilio. Ndiyo njia pekee ya kurejesha pochi yako.';
@override
String get revealSeedPhrase => 'Onyesha maneno ya mbegu';
@@ -117,7 +121,8 @@ class L10nSw extends L10n {
String get enterSeedPhrase => 'Ingiza maneno yako ya mbegu';
@override
String get enterSeedDescription => 'Andika maneno 12 au 24 yakitengwa na nafasi.';
String get enterSeedDescription =>
'Andika maneno 12 au 24 yakitengwa na nafasi.';
@override
String get seedPlaceholder => 'neno1 neno2 neno3 ...';
@@ -273,7 +278,8 @@ class L10nSw extends L10n {
String get noActiveMint => 'Hakuna mint inayofanya kazi';
@override
String get offlineModeMessage => 'Hakuna muunganisho. Kutumia hali ya nje ya mtandao...';
String get offlineModeMessage =>
'Hakuna muunganisho. Kutumia hali ya nje ya mtandao...';
@override
String get confirmSend => 'Thibitisha kutuma';
@@ -310,11 +316,50 @@ class L10nSw extends L10n {
}
@override
String get keepTokenWarning => 'Hifadhi tokeni hii hadi mpokeaji aidai. Ukiipoteza, utapoteza fedha.';
String get keepTokenWarning =>
'Hifadhi tokeni hii hadi mpokeaji aidai. Ukiipoteza, utapoteza fedha.';
@override
String get tokenCopiedToClipboard => 'Tokeni imenakiliwa kwenye ubao';
@override
String get copyAsEmoji => 'Nakili kama emoji';
@override
String get emojiCopiedToClipboard => 'Tokeni imenakiliwa kama emoji 🥜';
@override
String get peanutDecodeError =>
'Imeshindwa kusimbua tokeni ya emoji. Inaweza kuwa imeharibika.';
@override
String get nfcWrite => 'Andika kwenye NFC tag';
@override
String get nfcRead => 'Soma NFC tag';
@override
String get nfcHoldNear => 'Karibia kifaa kwenye NFC tag...';
@override
String get nfcWriteSuccess => 'Tokeni imeandikwa kwenye NFC tag';
@override
String nfcWriteError(String error) {
return 'Hitilafu ya NFC kuandika: $error';
}
@override
String nfcReadError(String error) {
return 'Hitilafu ya NFC kusoma: $error';
}
@override
String get nfcDisabled => 'NFC imezimwa. Iwashe katika Mipangilio.';
@override
String get nfcUnsupported => 'Kifaa hiki hakitumii NFC';
@override
String get amountToDeposit => 'Kiasi cha kuweka:';
@@ -665,7 +710,8 @@ class L10nSw extends L10n {
String get close => 'Funga';
@override
String get aboutDescription => 'Pochi ya Cashu yenye DNA ya Cuba kwa ulimwengu wote. Ndugu wa La Chispa.';
String get aboutDescription =>
'Pochi ya Cashu yenye DNA ya Cuba kwa ulimwengu wote. Ndugu wa LaChispa.';
@override
String get couldNotOpenLink => 'Haikuweza kufungua kiungo';
@@ -677,7 +723,8 @@ class L10nSw extends L10n {
String get actionIrreversible => 'Kitendo hiki hakiwezi kutenduliwa';
@override
String get deleteWalletWarning => 'Data zote zitafutwa ikiwa ni pamoja na maneno yako ya mbegu na tokeni. Hakikisha una nakala.';
String get deleteWalletWarning =>
'Data zote zitafutwa ikiwa ni pamoja na maneno yako ya mbegu na tokeni. Hakikisha una nakala.';
@override
String get typeDeleteToConfirm => 'Andika \"FUTA\" kuthibitisha:';
@@ -694,13 +741,15 @@ class L10nSw extends L10n {
String get recoverTokensTitle => 'Rejesha tokeni';
@override
String get recoverTokensDescription => 'Changanua mint kurejesha tokeni zinazohusiana na maneno yako ya mbegu (NUT-13)';
String get recoverTokensDescription =>
'Changanua mint kurejesha tokeni zinazohusiana na maneno yako ya mbegu (NUT-13)';
@override
String get useCurrentSeedPhrase => 'Tumia maneno yangu ya mbegu ya sasa';
@override
String get scanWithSavedWords => 'Changanua mint na maneno 12 yaliyohifadhiwa';
String get scanWithSavedWords =>
'Changanua mint na maneno 12 yaliyohifadhiwa';
@override
String get useOtherSeedPhrase => 'Tumia maneno mengine ya mbegu';
@@ -732,7 +781,8 @@ class L10nSw extends L10n {
String get mnemonicMustHaveWords => 'Mnemonic lazima iwe na maneno 12 au 24';
@override
String get noConnectedMintsToScan => 'Hakuna mint zilizounganishwa za kuchanganua';
String get noConnectedMintsToScan =>
'Hakuna mint zilizounganishwa za kuchanganua';
@override
String recoveredTokens(String tokens, int mints) {
@@ -740,7 +790,8 @@ class L10nSw extends L10n {
}
@override
String get scanCompleteNoTokens => 'Uchanganuzi umekamilika. Hakuna tokeni mpya zilizopatikana.';
String get scanCompleteNoTokens =>
'Uchanganuzi umekamilika. Hakuna tokeni mpya zilizopatikana.';
@override
String mintsWithError(int count) {
@@ -763,7 +814,8 @@ class L10nSw extends L10n {
}
@override
String get noTokensForMnemonic => 'Hakuna tokeni zilizopatikana zinazohusiana na mnemonic hiyo.';
String get noTokensForMnemonic =>
'Hakuna tokeni zilizopatikana zinazohusiana na mnemonic hiyo.';
@override
String get noConnectedMints => 'Hakuna mint zilizounganishwa';
@@ -843,7 +895,8 @@ class L10nSw extends L10n {
String get deleteMintConfirmTitle => 'Futa mint';
@override
String get deleteMintConfirmMessage => 'Ukiwa na salio katika mint hii, itapotea. Una uhakika?';
String get deleteMintConfirmMessage =>
'Ukiwa na salio katika mint hii, itapotea. Una uhakika?';
@override
String get delete => 'Futa';
@@ -854,6 +907,9 @@ class L10nSw extends L10n {
@override
String get selectAll => 'Zote';
@override
String get deselectAll => 'Hakuna';
@override
String get selectNotesToSend => 'Chagua noti unazotaka kutuma:';
@@ -893,19 +949,23 @@ class L10nSw extends L10n {
String get tokenSavedForLater => 'Tokeni imehifadhiwa kudai baadaye';
@override
String get noConnectionTokenSaved => 'Hakuna muunganisho. Tokeni imehifadhiwa kudai baadaye.';
String get noConnectionTokenSaved =>
'Hakuna muunganisho. Tokeni imehifadhiwa kudai baadaye.';
@override
String get unknownMintOffline => 'Tokeni hii ni kutoka mint isiyojulikana. Unganisha na mtandao kuongeza na kudai tokeni.';
String get unknownMintOffline =>
'Tokeni hii ni kutoka mint isiyojulikana. Unganisha na mtandao kuongeza na kudai tokeni.';
@override
String get noConnectionTryLater => 'Hakuna muunganisho na mint. Jaribu baadaye.';
String get noConnectionTryLater =>
'Hakuna muunganisho na mint. Jaribu baadaye.';
@override
String get saveTokenError => 'Hitilafu ya kuhifadhi tokeni. Jaribu tena.';
@override
String get pendingTokenLimitReached => 'Kikomo cha tokeni zinazosubiri kimefikiwa (upeo 50)';
String get pendingTokenLimitReached =>
'Kikomo cha tokeni zinazosubiri kimefikiwa (upeo 50)';
@override
String get filterToReceive => 'Za kupokea';
@@ -973,7 +1033,8 @@ class L10nSw extends L10n {
String get pointCameraAtQr => 'Elekeza kamera kwenye msimbo wa QR';
@override
String get pointCameraAtCashuQr => 'Elekeza kamera kwenye QR ya tokeni ya Cashu';
String get pointCameraAtCashuQr =>
'Elekeza kamera kwenye QR ya tokeni ya Cashu';
@override
String get pointCameraAtInvoiceQr => 'Elekeza kamera kwenye QR ya ankara';
@@ -982,10 +1043,12 @@ class L10nSw extends L10n {
String get unrecognizedQrCode => 'Msimbo wa QR haujatambuliwa';
@override
String get scanCashuTokenHint => 'Changanua tokeni ya Cashu (cashuA... au cashuB...)';
String get scanCashuTokenHint =>
'Changanua tokeni ya Cashu (cashuA... au cashuB...)';
@override
String get scanLightningInvoiceHint => 'Changanua ankara ya Lightning (lnbc...)';
String get scanLightningInvoiceHint =>
'Changanua ankara ya Lightning (lnbc...)';
@override
String get addMintQuestion => 'Ongeza mint hii?';
@@ -1006,7 +1069,8 @@ class L10nSw extends L10n {
String get p2pkExperimental => 'P2PK ni ya majaribio. Tumia kwa uangalifu.';
@override
String get p2pkPendingSendWarning => 'Una usafirishaji wa P2PK unaosubiri. Nenda kwenye historia na usasishe baada ya mpokeaji kudai tokeni.';
String get p2pkPendingSendWarning =>
'Una usafirishaji wa P2PK unaosubiri. Nenda kwenye historia na usasishe baada ya mpokeaji kudai tokeni.';
@override
String get p2pkExperimentalShort => 'Majaribio';
@@ -1072,13 +1136,15 @@ class L10nSw extends L10n {
String get p2pkDeleteTitle => 'Futa ufunguo';
@override
String get p2pkDeleteConfirm => 'Futa ufunguo huu? Hutaweza kupokea tokeni zilizofungwa kwake.';
String get p2pkDeleteConfirm =>
'Futa ufunguo huu? Hutaweza kupokea tokeni zilizofungwa kwake.';
@override
String get p2pkRequiresConnection => 'P2PK inahitaji muunganisho kwa mint';
@override
String get p2pkErrorMaxKeysReached => 'Idadi ya juu ya funguo zilizoingizwa imefikiwa (10)';
String get p2pkErrorMaxKeysReached =>
'Idadi ya juu ya funguo zilizoingizwa imefikiwa (10)';
@override
String get p2pkErrorInvalidNsec => 'nsec batili';
@@ -1091,7 +1157,4 @@ class L10nSw extends L10n {
@override
String get p2pkErrorCannotDeletePrimary => 'Haiwezekani kufuta ufunguo mkuu';
@override
String get p2pkSendComingSoon => 'Inakuja hivi karibuni';
}
+42 -5
View File
@@ -1,5 +1,5 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
@@ -315,6 +315,43 @@ class L10nZh extends L10n {
@override
String get tokenCopiedToClipboard => '代币已复制到剪贴板';
@override
String get copyAsEmoji => '复制为表情符号';
@override
String get emojiCopiedToClipboard => '代币已复制为表情符号 🥜';
@override
String get peanutDecodeError => '无法解码表情符号代币。可能已损坏。';
@override
String get nfcWrite => '写入NFC标签';
@override
String get nfcRead => '读取NFC标签';
@override
String get nfcHoldNear => '将设备靠近NFC标签...';
@override
String get nfcWriteSuccess => '代币已写入NFC标签';
@override
String nfcWriteError(String error) {
return 'NFC写入错误:$error';
}
@override
String nfcReadError(String error) {
return 'NFC读取错误:$error';
}
@override
String get nfcDisabled => 'NFC已禁用,请在设置中启用。';
@override
String get nfcUnsupported => '此设备不支持NFC';
@override
String get amountToDeposit => '存入金额:';
@@ -665,7 +702,7 @@ class L10nZh extends L10n {
String get close => '关闭';
@override
String get aboutDescription => '具有古巴基因的 Cashu 钱包,面向全世界。La Chispa 的兄弟项目。';
String get aboutDescription => '具有古巴基因的 Cashu 钱包,面向全世界。LaChispa 的兄弟项目。';
@override
String get couldNotOpenLink => '无法打开链接';
@@ -854,6 +891,9 @@ class L10nZh extends L10n {
@override
String get selectAll => '全部';
@override
String get deselectAll => '';
@override
String get selectNotesToSend => '选择要发送的票据:';
@@ -1091,7 +1131,4 @@ class L10nZh extends L10n {
@override
String get p2pkErrorCannotDeletePrimary => '无法删除主密钥';
@override
String get p2pkSendComingSoon => '即将推出';
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "Guarde este token até que o destinatário o resgate. Se você perdê-lo, perderá os fundos.",
"tokenCopiedToClipboard": "Token copiado para área de transferência",
"copyAsEmoji": "Copiar como emoji",
"emojiCopiedToClipboard": "Token copiado como emoji 🥜",
"peanutDecodeError": "Não foi possível decodificar o token emoji. Pode estar corrompido.",
"nfcWrite": "Escrever em tag NFC",
"nfcRead": "Ler tag NFC",
"nfcHoldNear": "Aproxime o dispositivo da tag NFC...",
"nfcWriteSuccess": "Token escrito na tag NFC",
"nfcWriteError": "Erro NFC ao escrever: {error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "Erro NFC ao ler: {error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFC está desativado. Ative nas Configurações.",
"nfcUnsupported": "Este dispositivo não suporta NFC",
"amountToDeposit": "Valor a depositar:",
"descriptionOptional": "Descrição (opcional):",
@@ -296,7 +310,7 @@
}
},
"close": "Fechar",
"aboutDescription": "Uma wallet Cashu com DNA cubano para o mundo inteiro. Irmã de La Chispa.",
"aboutDescription": "Uma wallet Cashu com DNA cubano para o mundo inteiro. Irmã de LaChispa.",
"couldNotOpenLink": "Não foi possível abrir o link",
"deleteWalletQuestion": "Apagar wallet?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "nsec inválido",
"p2pkErrorKeyAlreadyExists": "Esta chave já existe",
"p2pkErrorKeyNotFound": "Chave não encontrada",
"p2pkErrorCannotDeletePrimary": "Não é possível excluir a chave principal",
"p2pkSendComingSoon": "Em breve disponível"
"p2pkErrorCannotDeletePrimary": "Não é possível excluir a chave principal"
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "Сохраните этот токен, пока получатель не заберёт его. Если потеряете — потеряете средства.",
"tokenCopiedToClipboard": "Токен скопирован в буфер обмена",
"copyAsEmoji": "Скопировать как эмодзи",
"emojiCopiedToClipboard": "Токен скопирован как эмодзи 🥜",
"peanutDecodeError": "Не удалось декодировать эмодзи-токен. Возможно, он повреждён.",
"nfcWrite": "Записать на NFC-метку",
"nfcRead": "Читать NFC-метку",
"nfcHoldNear": "Поднесите устройство к NFC-метке...",
"nfcWriteSuccess": "Токен записан на NFC-метку",
"nfcWriteError": "Ошибка записи NFC: {error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "Ошибка чтения NFC: {error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFC отключён. Включите в Настройках.",
"nfcUnsupported": "Это устройство не поддерживает NFC",
"amountToDeposit": "Сумма для пополнения:",
"descriptionOptional": "Описание (необязательно):",
@@ -296,7 +310,7 @@
}
},
"close": "Закрыть",
"aboutDescription": "Cashu кошелёк с кубинской ДНК для всего мира. Брат La Chispa.",
"aboutDescription": "Cashu кошелёк с кубинской ДНК для всего мира. Брат LaChispa.",
"couldNotOpenLink": "Не удалось открыть ссылку",
"deleteWalletQuestion": "Удалить кошелёк?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "Недействительный nsec",
"p2pkErrorKeyAlreadyExists": "Этот ключ уже существует",
"p2pkErrorKeyNotFound": "Ключ не найден",
"p2pkErrorCannotDeletePrimary": "Невозможно удалить основной ключ",
"p2pkSendComingSoon": "Скоро будет доступно"
"p2pkErrorCannotDeletePrimary": "Невозможно удалить основной ключ"
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "Hifadhi tokeni hii hadi mpokeaji aidai. Ukiipoteza, utapoteza fedha.",
"tokenCopiedToClipboard": "Tokeni imenakiliwa kwenye ubao",
"copyAsEmoji": "Nakili kama emoji",
"emojiCopiedToClipboard": "Tokeni imenakiliwa kama emoji 🥜",
"peanutDecodeError": "Imeshindwa kusimbua tokeni ya emoji. Inaweza kuwa imeharibika.",
"nfcWrite": "Andika kwenye NFC tag",
"nfcRead": "Soma NFC tag",
"nfcHoldNear": "Karibia kifaa kwenye NFC tag...",
"nfcWriteSuccess": "Tokeni imeandikwa kwenye NFC tag",
"nfcWriteError": "Hitilafu ya NFC kuandika: {error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "Hitilafu ya NFC kusoma: {error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFC imezimwa. Iwashe katika Mipangilio.",
"nfcUnsupported": "Kifaa hiki hakitumii NFC",
"amountToDeposit": "Kiasi cha kuweka:",
"descriptionOptional": "Maelezo (si lazima):",
@@ -296,7 +310,7 @@
}
},
"close": "Funga",
"aboutDescription": "Pochi ya Cashu yenye DNA ya Cuba kwa ulimwengu wote. Ndugu wa La Chispa.",
"aboutDescription": "Pochi ya Cashu yenye DNA ya Cuba kwa ulimwengu wote. Ndugu wa LaChispa.",
"couldNotOpenLink": "Haikuweza kufungua kiungo",
"deleteWalletQuestion": "Futa pochi?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "nsec batili",
"p2pkErrorKeyAlreadyExists": "Ufunguo huu tayari upo",
"p2pkErrorKeyNotFound": "Ufunguo haujapatikana",
"p2pkErrorCannotDeletePrimary": "Haiwezekani kufuta ufunguo mkuu",
"p2pkSendComingSoon": "Inakuja hivi karibuni"
"p2pkErrorCannotDeletePrimary": "Haiwezekani kufuta ufunguo mkuu"
}
+16 -3
View File
@@ -137,6 +137,20 @@
},
"keepTokenWarning": "请保留此代币直到收款方领取。如果丢失,资金将无法找回。",
"tokenCopiedToClipboard": "代币已复制到剪贴板",
"copyAsEmoji": "复制为表情符号",
"emojiCopiedToClipboard": "代币已复制为表情符号 🥜",
"peanutDecodeError": "无法解码表情符号代币。可能已损坏。",
"nfcWrite": "写入NFC标签",
"nfcRead": "读取NFC标签",
"nfcHoldNear": "将设备靠近NFC标签...",
"nfcWriteSuccess": "代币已写入NFC标签",
"nfcWriteError": "NFC写入错误:{error}",
"@nfcWriteError": { "placeholders": { "error": { "type": "String" } } },
"nfcReadError": "NFC读取错误:{error}",
"@nfcReadError": { "placeholders": { "error": { "type": "String" } } },
"nfcDisabled": "NFC已禁用,请在设置中启用。",
"nfcUnsupported": "此设备不支持NFC",
"amountToDeposit": "存入金额:",
"descriptionOptional": "描述(可选):",
@@ -296,7 +310,7 @@
}
},
"close": "关闭",
"aboutDescription": "具有古巴基因的 Cashu 钱包,面向全世界。La Chispa 的兄弟项目。",
"aboutDescription": "具有古巴基因的 Cashu 钱包,面向全世界。LaChispa 的兄弟项目。",
"couldNotOpenLink": "无法打开链接",
"deleteWalletQuestion": "删除钱包?",
@@ -520,6 +534,5 @@
"p2pkErrorInvalidNsec": "无效的nsec",
"p2pkErrorKeyAlreadyExists": "此密钥已存在",
"p2pkErrorKeyNotFound": "未找到密钥",
"p2pkErrorCannotDeletePrimary": "无法删除主密钥",
"p2pkSendComingSoon": "即将推出"
"p2pkErrorCannotDeletePrimary": "无法删除主密钥"
}
+25 -74
View File
@@ -967,11 +967,12 @@ class WalletProvider extends ChangeNotifier {
}
/// Prepara un envío P2PK (bloqueado a una clave pública).
/// includeFee: true asegura que se reserven proofs para cubrir el swap fee (CDK 0.15+).
Future<PreparedSend> prepareSendP2pk(BigInt amount, String pubkey) async {
final wallet = await getActiveWallet();
return await wallet.prepareSend(
amount: amount,
opts: SendOptions(pubkey: pubkey),
opts: SendOptions(pubkey: pubkey, includeFee: true),
);
}
@@ -1028,91 +1029,41 @@ class WalletProvider extends ChangeNotifier {
}
/// Método de conveniencia: prepara y confirma en un solo paso.
/// Si confirmSend falla, libera proofs reservados con cancelSend.
Future<String> sendTokens(BigInt amount, String? memo) async {
final wallet = await getActiveWallet();
final balanceBefore = await wallet.balance();
debugPrint('[SEND DEBUG] Normal send - balance=$balanceBefore, amount=$amount');
await KeysetDebug.logCounters('BEFORE normal send');
final prepared = await prepareSend(amount);
debugPrint('[SEND DEBUG] prepareSend OK - fee=${prepared.fee}');
final token = await confirmSend(prepared, memo);
final balanceAfter = await wallet.balance();
debugPrint('[SEND DEBUG] confirmSend OK - balance after=$balanceAfter');
await KeysetDebug.logCounters('AFTER normal send');
return token;
}
/// Verifica si el mint activo soporta P2PK sin el bug de fees.
/// Bug CDK: prepare_send con P2PK (force_swap=true) no reserva proofs
/// para el swap fee. Solo mints con ppk=0 funcionan correctamente.
/// Ver P2PK_SEND_BUG.md para análisis completo.
Future<void> _checkP2pkMintCompatibility() async {
final mintUrl = _activeMintUrl!;
final unit = _activeUnit;
final ppk = await KeysetDebug.getInputFeePpk(mintUrl, unit);
debugPrint('[P2PK] mintUrl=$mintUrl, unit=$unit, ppk=$ppk');
if (ppk > 0) {
throw Exception(
'P2PK no disponible en este mint. '
'El mint $mintUrl cobra input fees (ppk=$ppk) y CDK tiene un bug '
'que impide P2PK sends en mints con fees. '
'Usa un mint con ppk=0 (ej: mint.cubabitcoin.org).',
);
debugPrint('[SEND] prepareSend OK - fee=${prepared.fee}');
try {
final token = await confirmSend(prepared, memo);
debugPrint('[SEND] Send completed');
return token;
} catch (e) {
try {
await cancelSend(prepared);
} catch (cancelErr) {
debugPrint('[SEND] cancelSend failed: $cancelErr');
}
rethrow;
}
}
/// Envía tokens P2PK (bloqueados a una clave pública).
/// Solo funciona en mints con ppk=0 debido a bug en CDK core.
/// Ver P2PK_SEND_BUG.md para detalles.
/// CDK 0.15+ con includeFee: true maneja correctamente mints con ppk>0.
/// Si confirmSend falla, libera proofs reservados con cancelSend.
Future<String> sendTokensP2pk(BigInt amount, String pubkey, String? memo) async {
final wallet = await getActiveWallet();
// Verificar compatibilidad del mint con P2PK
await _checkP2pkMintCompatibility();
// DEBUG: estado antes del P2PK send
final balanceBefore = await wallet.balance();
debugPrint('[P2PK DEBUG] ===== BEFORE P2PK SEND =====');
debugPrint('[P2PK DEBUG] Balance: $balanceBefore');
debugPrint('[P2PK DEBUG] Amount to send: $amount');
debugPrint('[P2PK DEBUG] Pubkey: ${pubkey.length > 16 ? pubkey.substring(0, 16) : pubkey}...');
// DEBUG: counters antes de prepareSend
await KeysetDebug.logCounters('BEFORE P2PK prepareSend');
// Paso 1: prepareSend P2PK
debugPrint('[P2PK DEBUG] Calling prepareSendP2pk...');
debugPrint('[P2PK] Sending $amount to ${pubkey.length > 16 ? pubkey.substring(0, 16) : pubkey}...');
final prepared = await prepareSendP2pk(amount, pubkey);
debugPrint('[P2PK DEBUG] prepareSendP2pk OK - fee=${prepared.fee}');
final balanceAfterPrepare = await wallet.balance();
debugPrint('[P2PK DEBUG] Balance after prepare: $balanceAfterPrepare');
// DEBUG: counters después de prepareSend
await KeysetDebug.logCounters('AFTER P2PK prepareSend');
// Paso 2: confirmSend
debugPrint('[P2PK DEBUG] Calling confirmSend...');
debugPrint('[P2PK] prepareSend OK - fee=${prepared.fee}');
try {
final token = await confirmSend(prepared, memo);
final balanceAfterSend = await wallet.balance();
debugPrint('[P2PK DEBUG] confirmSend OK!');
debugPrint('[P2PK DEBUG] Balance after send: $balanceAfterSend');
// DEBUG: counters después de send exitoso
await KeysetDebug.logCounters('AFTER P2PK send OK');
debugPrint('[P2PK] Send completed');
return token;
} catch (e) {
final balanceAfterError = await wallet.balance();
debugPrint('[P2PK DEBUG] confirmSend FAILED: $e');
debugPrint('[P2PK DEBUG] Balance after error: $balanceAfterError');
// DEBUG: counters después de send fallido
await KeysetDebug.logCounters('AFTER P2PK send FAILED');
try {
await cancelSend(prepared);
} catch (cancelErr) {
debugPrint('[P2PK] cancelSend failed: $cancelErr');
}
rethrow;
}
}
+195 -7
View File
@@ -8,6 +8,8 @@ import '../../core/constants/dimensions.dart';
import '../../core/utils/formatters.dart';
import '../../core/utils/incoming_data_parser.dart' hide TokenInfo;
import '../../core/utils/nostr_utils.dart';
import '../../core/utils/peanut_codec.dart';
import '../../core/services/nfc_service.dart';
import '../../widgets/common/gradient_background.dart';
import '../../widgets/common/glass_card.dart';
import '../../widgets/common/primary_button.dart';
@@ -46,9 +48,14 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
bool _showManualKey = false; // Toggle para mostrar/ocultar nsec
String? _manualKeyError;
// NFC
NfcState _nfcState = NfcState.unsupported;
bool _nfcReading = false;
@override
void initState() {
super.initState();
_checkNfc();
// Pre-cargar token inicial si existe
if (widget.initialToken != null && widget.initialToken!.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -60,11 +67,19 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
@override
void dispose() {
if (_nfcReading) NfcService.stopRead();
_tokenController.dispose();
_manualKeyController.dispose();
super.dispose();
}
Future<void> _checkNfc() async {
final state = await NfcService.checkState();
if (mounted) {
setState(() => _nfcState = state);
}
}
@override
Widget build(BuildContext context) {
return GradientBackground(
@@ -245,6 +260,9 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
// Botón pegar (expandido)
Expanded(child: _buildPasteButton()),
const SizedBox(width: 12),
// Botón NFC (siempre visible)
_buildNfcButton(),
const SizedBox(width: 12),
// Botón escanear QR
_buildScanButton(),
],
@@ -291,6 +309,134 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
);
}
Widget _buildNfcButton() {
final isNfcEnabled = _nfcState == NfcState.enabled;
final isNfcUnsupported = _nfcState == NfcState.unsupported;
return Tooltip(
message: isNfcUnsupported
? L10n.of(context)!.nfcUnsupported
: isNfcEnabled
? L10n.of(context)!.nfcRead
: L10n.of(context)!.nfcDisabled,
child: GestureDetector(
onTap: _toggleNfcRead,
child: Opacity(
opacity: isNfcUnsupported ? 0.3 : 1.0,
child: Container(
padding: const EdgeInsets.all(AppDimensions.paddingSmall + 4),
decoration: BoxDecoration(
color: _nfcReading
? AppColors.primaryAction.withValues(alpha: 0.3)
: Colors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _nfcReading
? AppColors.primaryAction
: Colors.white.withValues(alpha: 0.2),
width: 1,
),
),
child: Icon(
LucideIcons.nfc,
color: _nfcReading
? AppColors.primaryAction
: isNfcEnabled
? AppColors.textSecondary
: AppColors.textSecondary.withValues(alpha: 0.5),
size: 24,
),
),
),
),
);
}
void _toggleNfcRead() async {
// Cancel active session first (before state checks, in case NFC was
// disabled while a session was running)
if (_nfcReading) {
NfcService.stopRead();
setState(() => _nfcReading = false);
return;
}
// Re-check state in case user toggled NFC in settings
final currentState = await NfcService.checkState();
if (mounted) setState(() => _nfcState = currentState);
if (currentState == NfcState.unsupported) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcUnsupported),
backgroundColor: AppColors.textSecondary,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
return;
}
if (currentState == NfcState.disabled) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcDisabled),
backgroundColor: AppColors.warning,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
return;
}
setState(() => _nfcReading = true);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcHoldNear),
backgroundColor: AppColors.primaryAction,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
try {
await NfcService.startRead(
onTokenRead: (token) {
if (!mounted) return;
setState(() => _nfcReading = false);
_tokenController.text = token;
_onTokenChanged(token);
},
onError: (error) {
if (!mounted) return;
setState(() => _nfcReading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcReadError(error)),
backgroundColor: AppColors.error,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
);
},
);
} catch (e) {
if (!mounted) return;
setState(() => _nfcReading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcReadError(e.toString())),
backgroundColor: AppColors.error,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
);
}
}
Widget _buildScanButton() {
return GestureDetector(
onTap: _openScanner,
@@ -779,9 +925,29 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
Future<void> _pasteFromClipboard() async {
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
if (!mounted) return;
if (clipboardData?.text != null) {
_tokenController.text = clipboardData!.text!.trim();
_onTokenChanged(clipboardData.text!.trim());
var text = clipboardData!.text!.trim();
// Auto-decode peanut emoji format
if (PeanutCodec.isPeanut(text)) {
final decoded = PeanutCodec.decode(text);
if (decoded != null) {
text = decoded;
} else {
// Malformed peanut: clear field and show specific error
_tokenController.clear();
setState(() {
_isValidToken = false;
_tokenInfo = null;
_errorMessage = L10n.of(context)!.peanutDecodeError;
});
return;
}
}
_tokenController.text = text;
_onTokenChanged(text);
}
}
@@ -789,6 +955,28 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
final walletProvider = context.read<WalletProvider>();
final p2pkProvider = context.read<P2PKProvider>();
// Auto-decode peanut emoji format
var tokenValue = value.trim();
if (PeanutCodec.isPeanut(tokenValue)) {
final decoded = PeanutCodec.decode(tokenValue);
if (decoded != null) {
tokenValue = decoded;
// Update the text field with the decoded token
_tokenController.text = tokenValue;
_tokenController.selection = TextSelection.collapsed(
offset: tokenValue.length,
);
} else {
// Malformed peanut: show specific error
setState(() {
_isValidToken = false;
_tokenInfo = null;
_errorMessage = L10n.of(context)!.peanutDecodeError;
});
return;
}
}
setState(() {
_errorMessage = null;
_manualKeyError = null;
@@ -799,24 +987,24 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
_lockedToPubkeyHex = null;
_matchingKeyLabel = null;
if (value.isEmpty) {
if (tokenValue.isEmpty) {
_isValidToken = false;
_tokenInfo = null;
return;
}
// Parsear token real con cdk-flutter
final tokenInfo = walletProvider.parseToken(value.trim());
final tokenInfo = walletProvider.parseToken(tokenValue);
if (tokenInfo != null) {
_isValidToken = true;
_tokenInfo = tokenInfo;
// Detectar P2PK
_isP2PKLocked = p2pkProvider.isTokenLocked(value.trim());
_isP2PKLocked = p2pkProvider.isTokenLocked(tokenValue);
if (_isP2PKLocked) {
_lockedToPubkeyHex = p2pkProvider.extractLockedPubkey(value.trim());
_isLockedToUs = p2pkProvider.isTokenLockedToUs(value.trim());
_lockedToPubkeyHex = p2pkProvider.extractLockedPubkey(tokenValue);
_isLockedToUs = p2pkProvider.isTokenLockedToUs(tokenValue);
// Si es nuestra, buscar el label de la clave
if (_isLockedToUs && _lockedToPubkeyHex != null) {
+157 -45
View File
@@ -13,6 +13,7 @@ import '../../widgets/common/primary_button.dart';
import '../../widgets/common/numpad_widget.dart';
import '../../providers/wallet_provider.dart';
import '../../core/utils/nostr_utils.dart';
import '../../widgets/scanner/qr_scanner_widget.dart';
import 'share_token_screen.dart';
import 'offline_send_screen.dart';
@@ -276,57 +277,109 @@ class _SendScreenState extends State<SendScreen> {
Widget _buildP2PKSection() {
final l10n = L10n.of(context)!;
// P2PK Send deshabilitado temporalmente por bug en CDK (cdk-flutter usa CDK 0.13.4).
// Se habilitará cuando cdk-flutter actualice a CDK 0.14.x con el fix de include_fee.
const bool p2pkSendEnabled = false;
return Opacity(
opacity: p2pkSendEnabled ? 1.0 : 0.5,
child: GlassCard(
padding: const EdgeInsets.symmetric(
horizontal: AppDimensions.paddingMedium,
vertical: AppDimensions.paddingSmall,
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GlassCard(
padding: const EdgeInsets.symmetric(
horizontal: AppDimensions.paddingMedium,
vertical: AppDimensions.paddingSmall,
),
child: Row(
children: [
Icon(
LucideIcons.lock,
color: _useP2PK ? AppColors.primaryAction : AppColors.textSecondary,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.p2pkLockToKey,
style: const TextStyle(
fontFamily: 'Inter',
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
Text(
l10n.p2pkLockDescription,
style: TextStyle(
fontFamily: 'Inter',
fontSize: 12,
color: AppColors.textSecondary.withValues(alpha: 0.7),
),
),
],
),
),
Switch(
value: _useP2PK,
onChanged: (value) => setState(() => _useP2PK = value),
activeColor: AppColors.primaryAction,
),
],
),
),
child: Row(
children: [
Icon(
LucideIcons.lock,
color: AppColors.textSecondary,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.p2pkLockToKey,
style: TextStyle(
// Campo de pubkey cuando P2PK está activo
if (_useP2PK) ...[
const SizedBox(height: AppDimensions.paddingSmall),
GlassCard(
padding: const EdgeInsets.all(AppDimensions.paddingSmall),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _pubkeyController,
onChanged: _validatePubkey,
maxLines: 1,
style: const TextStyle(
fontFamily: 'Inter',
fontSize: 14,
color: Colors.white,
),
decoration: InputDecoration(
hintText: l10n.p2pkReceiverPubkey,
hintStyle: TextStyle(
fontFamily: 'Inter',
fontSize: 14,
fontWeight: FontWeight.w500,
color: p2pkSendEnabled ? Colors.white : AppColors.textSecondary,
color: Colors.white.withValues(alpha: 0.3),
),
),
Text(
l10n.p2pkSendComingSoon,
style: TextStyle(
border: InputBorder.none,
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(LucideIcons.clipboard, size: 20),
color: AppColors.textSecondary,
onPressed: _pastePubkey,
),
IconButton(
icon: const Icon(LucideIcons.scanLine, size: 20),
color: AppColors.textSecondary,
tooltip: l10n.scanQrCode,
onPressed: _scanPubkey,
),
],
),
errorText: _pubkeyError,
errorStyle: TextStyle(
fontFamily: 'Inter',
fontSize: 12,
color: AppColors.textSecondary.withValues(alpha: 0.7),
color: AppColors.error,
),
),
],
),
),
],
),
Switch(
value: false,
onChanged: null,
activeColor: AppColors.primaryAction,
),
],
),
),
),
],
],
);
}
@@ -344,12 +397,71 @@ class _SendScreenState extends State<SendScreen> {
Future<void> _pastePubkey() async {
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
if (clipboardData?.text != null) {
_pubkeyController.text = clipboardData!.text!;
_validatePubkey(clipboardData.text!);
if (!mounted) return;
final text = clipboardData?.text?.trim();
if (text != null && text.isNotEmpty) {
_pubkeyController.text = text;
_validatePubkey(text);
}
}
Future<void> _scanPubkey() async {
final result = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: const Icon(LucideIcons.x, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
title: Text(
L10n.of(context)!.scanQrCode,
style: const TextStyle(
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
centerTitle: true,
),
body: QrScannerWidget(
onDetect: (data) {
final trimmed = data.trim();
if (NostrUtils.isValidP2PKPubkey(trimmed)) {
Navigator.pop(context, trimmed);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.p2pkInvalidPubkey),
duration: const Duration(seconds: 2),
),
);
}
},
onError: (error) {
debugPrint('[P2PK Scan] $error');
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.cameraPermissionDenied),
backgroundColor: AppColors.error,
),
);
},
),
),
),
);
if (!mounted || result == null) return;
_pubkeyController.text = result;
_validatePubkey(result);
}
Widget _buildErrorMessage() {
return Container(
margin: const EdgeInsets.only(bottom: AppDimensions.paddingMedium),
+165 -1
View File
@@ -9,6 +9,8 @@ import 'package:elcaju/l10n/app_localizations.dart';
import '../../core/constants/colors.dart';
import '../../core/constants/dimensions.dart';
import '../../core/utils/formatters.dart';
import '../../core/utils/peanut_codec.dart';
import '../../core/services/nfc_service.dart';
import '../../widgets/common/gradient_background.dart';
import '../../widgets/common/glass_card.dart';
import '../../widgets/common/primary_button.dart';
@@ -44,18 +46,31 @@ class _ShareTokenScreenState extends State<ShareTokenScreen> {
int _currentInterval = _intervalMedium;
String _speedLabel = 'M';
// NFC
NfcState _nfcState = NfcState.unsupported;
bool _nfcWriting = false;
@override
void initState() {
super.initState();
_encodeTokenToUR();
_checkNfc();
}
@override
void dispose() {
_animationTimer?.cancel();
if (_nfcWriting) NfcService.stopEmulating();
super.dispose();
}
Future<void> _checkNfc() async {
final state = await NfcService.checkState();
if (mounted) {
setState(() => _nfcState = state);
}
}
void _encodeTokenToUR() {
// Parsear el token string a objeto Token de cdk-flutter
final token = cdk.Token.parse(encoded: widget.token);
@@ -155,9 +170,14 @@ class _ShareTokenScreenState extends State<ShareTokenScreen> {
const SizedBox(height: AppDimensions.paddingMedium),
// Botones copiar y compartir
// Botones principales: copiar y compartir
_buildActionButtons(context),
const SizedBox(height: AppDimensions.paddingSmall),
// Botones secundarios: bean (peanut) y NFC
_buildSecondaryActions(context),
const SizedBox(height: AppDimensions.paddingLarge),
// Advertencia
@@ -450,6 +470,75 @@ class _ShareTokenScreenState extends State<ShareTokenScreen> {
);
}
Widget _buildSecondaryActions(BuildContext context) {
final isNfcEnabled = _nfcState == NfcState.enabled;
final isNfcUnsupported = _nfcState == NfcState.unsupported;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Copiar como emoji (peanut encoding)
Tooltip(
message: L10n.of(context)!.copyAsEmoji,
child: GestureDetector(
onTap: () => _copyPeanut(context),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.07),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.white.withValues(alpha: 0.1),
width: 1,
),
),
child: Icon(LucideIcons.bean, color: AppColors.primaryAction, size: 20),
),
),
),
const SizedBox(width: 12),
// NFC - siempre visible con 3 estados
Tooltip(
message: isNfcUnsupported
? L10n.of(context)!.nfcUnsupported
: isNfcEnabled
? L10n.of(context)!.nfcWrite
: L10n.of(context)!.nfcDisabled,
child: GestureDetector(
onTap: () => _writeNfc(context),
child: Opacity(
opacity: isNfcUnsupported ? 0.3 : 1.0,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _nfcWriting
? AppColors.primaryAction.withValues(alpha: 0.3)
: Colors.white.withValues(alpha: 0.07),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _nfcWriting
? AppColors.primaryAction
: Colors.white.withValues(alpha: 0.1),
width: 1,
),
),
child: Icon(
LucideIcons.nfc,
color: _nfcWriting
? AppColors.primaryAction
: isNfcEnabled
? AppColors.textSecondary
: AppColors.textSecondary.withValues(alpha: 0.5),
size: 20,
),
),
),
),
),
],
);
}
Widget _buildWarning() {
return Container(
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
@@ -492,6 +581,19 @@ class _ShareTokenScreenState extends State<ShareTokenScreen> {
);
}
void _copyPeanut(BuildContext context) {
final peanut = PeanutCodec.encode(widget.token);
Clipboard.setData(ClipboardData(text: peanut));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.emojiCopiedToClipboard),
backgroundColor: AppColors.success,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
}
void _shareToken(BuildContext context) async {
final memo = widget.memo != null && widget.memo!.isNotEmpty
? '\n"${widget.memo}"'
@@ -508,6 +610,68 @@ class _ShareTokenScreenState extends State<ShareTokenScreen> {
);
}
void _writeNfc(BuildContext context) async {
// Toggle off: stop emulating
if (_nfcWriting) {
await NfcService.stopEmulating();
setState(() => _nfcWriting = false);
return;
}
// Re-check state in case user toggled NFC in settings
final currentState = await NfcService.checkState();
if (mounted) setState(() => _nfcState = currentState);
if (currentState == NfcState.unsupported) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcUnsupported),
backgroundColor: AppColors.textSecondary,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
return;
}
if (currentState == NfcState.disabled) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcDisabled),
backgroundColor: AppColors.warning,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
return;
}
try {
await NfcService.startEmulating(widget.token);
setState(() => _nfcWriting = true);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcHoldNear),
backgroundColor: AppColors.primaryAction,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
} catch (e) {
if (!mounted) return;
setState(() => _nfcWriting = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.nfcWriteError(e.toString())),
backgroundColor: AppColors.error,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
);
}
}
void _goToHome(BuildContext context) {
Navigator.of(context).popUntil((route) => route.isFirst);
}
+89 -39
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:elcaju/l10n/app_localizations.dart';
@@ -24,6 +25,25 @@ class SettingsScreen extends StatefulWidget {
}
class _SettingsScreenState extends State<SettingsScreen> {
String _appVersion = '';
@override
void initState() {
super.initState();
_loadVersion();
}
Future<void> _loadVersion() async {
try {
final info = await PackageInfo.fromPlatform();
if (!mounted) return;
setState(() => _appVersion = info.version);
} catch (_) {
if (!mounted) return;
setState(() => _appVersion = '');
}
}
@override
Widget build(BuildContext context) {
final l10n = L10n.of(context)!;
@@ -124,7 +144,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_buildInfoTile(
icon: LucideIcons.tag,
title: l10n.version,
subtitle: '0.0.1',
subtitle: _appVersion,
),
_buildSettingTile(
icon: LucideIcons.info,
@@ -620,7 +640,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
const SizedBox(height: 4),
Text(
'v0.0.1',
'v$_appVersion',
style: TextStyle(
fontFamily: 'Inter',
fontSize: 14,
@@ -638,43 +658,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
L10n.of(context)!.aboutDescription,
style: TextStyle(
fontFamily: 'Inter',
fontSize: 14,
color: AppColors.textSecondary.withValues(alpha: 0.7),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
LucideIcons.bitcoin,
color: AppColors.secondaryAction,
size: 16,
),
const SizedBox(width: 8),
Text(
'Cuba Bitcoin',
style: TextStyle(
fontFamily: 'Inter',
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.secondaryAction,
),
),
],
),
),
_buildAboutDescription(context),
],
),
actions: [
@@ -693,6 +677,72 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
}
Widget _buildAboutDescription(BuildContext context) {
final description = L10n.of(context)!.aboutDescription;
const keyword = 'LaChispa';
final index = description.indexOf(keyword);
if (index == -1) {
return Text(
description,
style: TextStyle(
fontFamily: 'Inter',
fontSize: 14,
color: AppColors.textSecondary.withValues(alpha: 0.7),
),
textAlign: TextAlign.center,
);
}
final before = description.substring(0, index);
final after = description.substring(index + keyword.length);
return RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: TextStyle(
fontFamily: 'Inter',
fontSize: 14,
color: AppColors.textSecondary.withValues(alpha: 0.7),
),
children: [
TextSpan(text: before),
WidgetSpan(
alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic,
child: GestureDetector(
onTap: () => _openLaChispa(context),
child: Text(
keyword,
style: TextStyle(
fontFamily: 'Inter',
fontSize: 14,
color: AppColors.secondaryAction,
decoration: TextDecoration.underline,
decorationColor: AppColors.secondaryAction,
),
),
),
),
TextSpan(text: after),
],
),
);
}
Future<void> _openLaChispa(BuildContext context) async {
final url = Uri.parse('https://app.lachispa.me');
try {
await launchUrl(url, mode: LaunchMode.externalApplication);
} catch (_) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.couldNotOpenLink),
backgroundColor: AppColors.error,
),
);
}
}
}
/// 4. Abrir GitHub
Future<void> _openGitHub() async {
final url = Uri.parse('https://github.com/Forte11Cuba/elcaju');
+16 -8
View File
@@ -693,6 +693,7 @@ class _TransactionDetailScreenState extends State<_TransactionDetailScreen> {
late final bool _isLightning;
String? _tokenOrInvoice;
late final bool _shouldShowQR;
bool _isLoadingPendingInvoice = false;
@override
void initState() {
@@ -720,13 +721,20 @@ class _TransactionDetailScreenState extends State<_TransactionDetailScreen> {
/// Busca el invoice en pending mint invoices si no hay metadata.
Future<void> _loadPendingMintInvoice() async {
final invoice = await widget.walletProvider.findPendingMintInvoice(
widget.transaction.mintUrl,
widget.transaction.unit,
);
if (invoice != null && mounted) {
setState(() => _isLoadingPendingInvoice = true);
String? invoice;
try {
invoice = await widget.walletProvider.findPendingMintInvoice(
widget.transaction.mintUrl,
widget.transaction.unit,
);
} catch (e, st) {
debugPrint('findPendingMintInvoice failed: $e\n$st');
}
if (mounted) {
setState(() {
_tokenOrInvoice = invoice;
_isLoadingPendingInvoice = false;
if (invoice != null) _tokenOrInvoice = invoice;
});
}
}
@@ -869,8 +877,8 @@ class _TransactionDetailScreenState extends State<_TransactionDetailScreen> {
const SizedBox(height: 32),
],
// Mensaje si no hay token/invoice
if (_tokenOrInvoice == null) ...[
// Mensaje si no hay token/invoice (ocultar mientras carga)
if (_tokenOrInvoice == null && !_isLoadingPendingInvoice) ...[
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
@@ -7,6 +7,7 @@ import Foundation
import flutter_secure_storage_macos
import mobile_scanner
import package_info_plus
import path_provider_foundation
import share_plus
import shared_preferences_foundation
@@ -16,6 +17,7 @@ import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
+66 -34
View File
@@ -86,7 +86,7 @@ packages:
description:
path: "."
ref: HEAD
resolved-ref: "3e0c77c22c807459d5d355e270ef4b5ad6a490c1"
resolved-ref: "7fb8580530d963c9ff09f23e55a1e56bccd8676b"
url: "https://github.com/cashubtc/cdk_flutter.git"
source: git
version: "0.1.0"
@@ -94,10 +94,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.1"
checked_yaml:
dependency: transitive
description:
@@ -118,18 +118,18 @@ packages:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.0"
version: "1.19.1"
convert:
dependency: transitive
description:
@@ -174,10 +174,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.3"
ffi:
dependency: transitive
description:
@@ -370,10 +370,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.19.0"
version: "0.20.2"
js:
dependency: transitive
description:
@@ -394,26 +394,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "10.0.7"
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.8"
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
lints:
dependency: transitive
description:
@@ -434,26 +434,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.17.0"
mime:
dependency: transitive
description:
@@ -470,6 +470,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.2.3"
ndef_record:
dependency: "direct main"
description:
name: ndef_record
sha256: "210ffb12284961cab9e44b99462143316d9a20cd992581170706069ef77d74a6"
url: "https://pub.dev"
source: hosted
version: "1.4.2"
nested:
dependency: transitive
description:
@@ -478,14 +486,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
nfc_manager:
dependency: "direct main"
description:
name: nfc_manager
sha256: "24c78b0e5702da53e7f8794d073624c0bee7cd99924f257cbd11f5d1c5866879"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
url: "https://pub.dev"
source: hosted
version: "9.0.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
path:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.9.1"
path_provider:
dependency: "direct main"
description:
@@ -743,18 +775,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
string_scanner:
dependency: transitive
description:
@@ -783,10 +815,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.3"
version: "0.7.10"
typed_data:
dependency: transitive
description:
@@ -879,10 +911,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
vm_service:
dependency: transitive
description:
@@ -932,5 +964,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.6.0 <4.0.0"
dart: ">=3.11.0 <4.0.0"
flutter: ">=3.27.0"
+7 -2
View File
@@ -1,10 +1,10 @@
name: elcaju
description: "ElCaju - Tu wallet de ecash privado"
publish_to: 'none'
version: 0.0.1+1
version: 0.1.0+2
environment:
sdk: ^3.6.0
sdk: ^3.11.0
dependencies:
flutter:
@@ -56,8 +56,13 @@ dependencies:
# UUID generator for pending tokens
uuid: ^4.3.3
# NFC for token sharing
nfc_manager: ^4.1.1
ndef_record: ^1.4.2
# BIP32 for NIP-06 key derivation (P2PK)
bip32: ^2.0.0
package_info_plus: ^9.0.0
dev_dependencies:
flutter_test: