diff --git a/i18n/translations/de-DE.json b/i18n/translations/de-DE.json index 9efac85e2..f53e58853 100644 --- a/i18n/translations/de-DE.json +++ b/i18n/translations/de-DE.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "Überprüfen, ob diese Adresse zu dieser Wallet gehört?", "Checked %d addresses with no matches.": "Überprüfte %d Adresse ohne Übereinstimmungen.", "Checking for SD card…": "SD-Karte wird gesucht…", + "Click to read": "Klicken zum Lesen", "Confirm Tamper Check Code": "Bestätigen Sie den Tamper Check Code", "Convert Datum": "Datum konvertieren", "Could not determine change address.": "Änderungsadresse konnte nicht ermittelt werden.", @@ -113,6 +114,7 @@ "Flash Map": "Flash-Karte", "Flash Tools": "Flash-Tools", "Flash filled with camera entropy": "Flash gefüllt mit Kameraentropie", + "Flip plate": "Platte umdrehen", "Flute Diameter": "Flötendurchmesser", "Free:": "Frei:", "From Storage": "Vom Speicher", @@ -178,6 +180,7 @@ "Network": "Netzwerk", "New Mnemonic": "Neue Mnemonic", "New firmware detected.": "Neue Firmware erkannt.", + "Next": "Weiter", "No": "Nein", "No Passphrase": "Keine Passphrase", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "Nicht-ASCII-Zeichen wurden in Ihrer Passphrase erkannt. Krux kann nicht garantieren, dass andere Wallets den gleichen Schlüssel ableiten.", @@ -205,6 +208,7 @@ "Plunge Rate": "Tauchrate", "Policy Type": "Richtlinientyp", "Poor entropy!": "Schlechte Entropie!", + "Position plate": "Platte positionieren", "Power": "Leistung", "Press PAGE to cancel.": "PAGE zum Abbrechen drücken.", "Press PAGE to toggle mode": "PAGE für Moduswechsel", @@ -348,6 +352,9 @@ "Word %d": "Wort %d", "Word Numbers": "Wortnummern", "Words": "Wörter", + "Words 1-12": "Wörter 1-12", + "Words 13-24": "Wörter 13-24", + "Words 7-12": "Wörter 7-12", "XOR Result": "XOR-Ergebnis", "XOR With": "XOR mit", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "XOR-Strommnemotechnik mit einer anderen? (Passphrase und Deskriptor werden verworfen)", @@ -374,4 +381,4 @@ "to utf8": "zu utf8", "unknown": "unbekannt", "was NOT FOUND in the first %d addresses": "wurde in den ersten %d Adressen nicht gefunden" -} \ No newline at end of file +} diff --git a/i18n/translations/es-MX.json b/i18n/translations/es-MX.json index ad181ab6a..7b97b07e0 100644 --- a/i18n/translations/es-MX.json +++ b/i18n/translations/es-MX.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "¿Verificar que la dirección pertenece a esta cartera?", "Checked %d addresses with no matches.": "Comprobado %d direcciones sin coincidencias.", "Checking for SD card…": "Buscando tarjeta SD…", + "Click to read": "Haga clic para leer", "Confirm Tamper Check Code": "Confirmar el código de verificación", "Convert Datum": "Convertir dato", "Could not determine change address.": "No se pudo determinar la dirección de cambio.", @@ -113,6 +114,7 @@ "Flash Map": "Mapa Flash", "Flash Tools": "Herramientas Flash", "Flash filled with camera entropy": "Flash lleno de entropía de cámara", + "Flip plate": "Voltee la placa", "Flute Diameter": "Diámetro de la Flauta", "Free:": "Libre:", "From Storage": "Desde el Almacenamiento", @@ -178,6 +180,7 @@ "Network": "Red", "New Mnemonic": "Nuevo Mnemónico", "New firmware detected.": "Nuevo firmware detectado.", + "Next": "Siguiente", "No": "No", "No Passphrase": "Sin Passphrase", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "Se detectaron caracteres no ASCII en su frase de contraseña. Krux no puede garantizar que otros monederos obtengan la misma clave.", @@ -205,6 +208,7 @@ "Plunge Rate": "Tasa de Caída", "Policy Type": "Tipo de póliza", "Poor entropy!": "Baja entropía!", + "Position plate": "Posicione la placa", "Power": "Potencia", "Press PAGE to cancel.": "Pulse PAGE para cancelar.", "Press PAGE to toggle mode": "PAGE para cambiar el modo", @@ -348,6 +352,9 @@ "Word %d": "Palabra %d", "Word Numbers": "Números de Palabra", "Words": "Palabras", + "Words 1-12": "Palabras 1-12", + "Words 13-24": "Palabras 13-24", + "Words 7-12": "Palabras 7-12", "XOR Result": "Resultado XOR", "XOR With": "XOR Con", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "¿XOR mnemónico actual con otro? (se descartarán la passphrase y el descriptor)", @@ -374,4 +381,4 @@ "to utf8": "a utf8", "unknown": "desconocido", "was NOT FOUND in the first %d addresses": "NO FUE ENCONTRADO en las primeras %d direcciones" -} \ No newline at end of file +} diff --git a/i18n/translations/fr-FR.json b/i18n/translations/fr-FR.json index e9be61249..490f375ba 100644 --- a/i18n/translations/fr-FR.json +++ b/i18n/translations/fr-FR.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "Vérifiez que l'adresse appartient à ce portefeuille ?", "Checked %d addresses with no matches.": "%d adresses vérifiées sans correspondance.", "Checking for SD card…": "Recherche de carte SD…", + "Click to read": "Cliquez pour lire", "Confirm Tamper Check Code": "Confirmer le code de non compromis", "Convert Datum": "Convertir le datum", "Could not determine change address.": "Impossible de déterminer l'adresse de monnaie.", @@ -113,6 +114,7 @@ "Flash Map": "Plan du Flash", "Flash Tools": "Outils Flash", "Flash filled with camera entropy": "Flash rempli par l'entropie de la caméra", + "Flip plate": "Retournez la plaque", "Flute Diameter": "Diamètre de flûte", "Free:": "Libre :", "From Storage": "Du stockage", @@ -178,6 +180,7 @@ "Network": "Réseau", "New Mnemonic": "Nouveau Mnémonique", "New firmware detected.": "Nouveau micrologiciel détecté.", + "Next": "Suivant", "No": "Non", "No Passphrase": "Pas de phrase secrète", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "Des caractères non ASCII ont été détectés dans votre phrase secrète. Krux ne peut garantir que d'autres portefeuilles obtiendront la même clé.", @@ -205,6 +208,7 @@ "Plunge Rate": "Taux de plongée", "Policy Type": "Type de politique", "Poor entropy!": "Entropie faible !", + "Position plate": "Positionnez la plaque", "Power": "Puissance", "Press PAGE to cancel.": "Appuyez sur PAGE pour annuler.", "Press PAGE to toggle mode": "PAGE pour changer de mode", @@ -348,6 +352,9 @@ "Word %d": "Mot %d", "Word Numbers": "Numéros de mots", "Words": "Mots", + "Words 1-12": "Mots 1-12", + "Words 13-24": "Mots 13-24", + "Words 7-12": "Mots 7-12", "XOR Result": "Résultat XOR", "XOR With": "XOR avec", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "XOR mnémonique actuel avec un autre ? (la phrase secrète et le descripteur seront supprimés)", @@ -374,4 +381,4 @@ "to utf8": "vers utf8", "unknown": "inconnu", "was NOT FOUND in the first %d addresses": "INTROUVABLE dans les %d premières adresses" -} \ No newline at end of file +} diff --git a/i18n/translations/ja-JP.json b/i18n/translations/ja-JP.json index a2dfc2969..446ce791d 100644 --- a/i18n/translations/ja-JP.json +++ b/i18n/translations/ja-JP.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "このアドレスがこのウォレットに属しているか確認しますか?", "Checked %d addresses with no matches.": "%d のアドレスを確認しましたが、一致するものはありませんでした.", "Checking for SD card…": "SDカードを確認しています…", + "Click to read": "クリックして読み取る", "Confirm Tamper Check Code": "改ざんチェックコードの確認", "Convert Datum": "データムの変換", "Could not determine change address.": "変更先住所を特定できませんでした.", @@ -113,6 +114,7 @@ "Flash Map": "フラッシュマップ", "Flash Tools": "Flashツール", "Flash filled with camera entropy": "カメラエントロピーで満たされたフラッシュ", + "Flip plate": "プレートを裏返す", "Flute Diameter": "フルートディアメーター", "Free:": "フリー:", "From Storage": "ストレージから", @@ -178,6 +180,7 @@ "Network": "ネットワーク", "New Mnemonic": "新しい Mnemonic", "New firmware detected.": "新しいファームウェアが検出されました.", + "Next": "次へ", "No": "いいえ", "No Passphrase": "パスフレーズなし", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "パスフレーズに非ASCII文字が検出されました.Kruxは、他のウォレットが同じキーを取得することを保証することはできません.", @@ -205,6 +208,7 @@ "Plunge Rate": "沈下率", "Policy Type": "証券の種類", "Poor entropy!": "低品質なエントロピー!", + "Position plate": "プレートを配置", "Power": "出力", "Press PAGE to cancel.": "PAGEを押してキャンセルします.", "Press PAGE to toggle mode": "PAGEを押してモードを切り替えます", @@ -348,6 +352,9 @@ "Word %d": "単語 %d", "Word Numbers": "単語番号", "Words": "単語", + "Words 1-12": "単語 1-12", + "Words 13-24": "単語 13-24", + "Words 7-12": "単語 7-12", "XOR Result": "XOR結果", "XOR With": "XOR With", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "XOR現在のニーモニックと別のニーモニック? (パスフレーズと記述子は破棄されます)", @@ -374,4 +381,4 @@ "to utf8": "utf 8へ", "unknown": "不明", "was NOT FOUND in the first %d addresses": "最初の%dアドレスに見つかりませんでした" -} \ No newline at end of file +} diff --git a/i18n/translations/ko-KR.json b/i18n/translations/ko-KR.json index 0124099b0..da2aab436 100644 --- a/i18n/translations/ko-KR.json +++ b/i18n/translations/ko-KR.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "해당 주소가 이 지갑에 속하는지 확인하시겠습니까?", "Checked %d addresses with no matches.": "일치하는 주소가 없는 %d 개를 확인했습니다.", "Checking for SD card…": "SD 카드 확인 중…", + "Click to read": "읽으려면 클릭", "Confirm Tamper Check Code": "탬퍼 체크 코드 확인", "Convert Datum": "날짜 변환", "Could not determine change address.": "변경 주소를 확인할 수 없습니다.", @@ -113,6 +114,7 @@ "Flash Map": "플래시 맵", "Flash Tools": "플래시 도구", "Flash filled with camera entropy": "카메라 엔트로피로 가득 찬 플래시", + "Flip plate": "플레이트 뒤집기", "Flute Diameter": "플루트 직경", "Free:": "여유 공간:", "From Storage": "저장공간에서 불러오기", @@ -178,6 +180,7 @@ "Network": "네트워크", "New Mnemonic": "새로운 니모닉", "New firmware detected.": "새로운 펌웨어가 감지되었습니다.", + "Next": "다음", "No": "아니요", "No Passphrase": "패스프레이즈 없음", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "암호에서 ASCII가 아닌 문자가 감지되었습니다. Krux는 다른 지갑이 동일한 키를 파생할 것이라고 보장할 수 없습니다.", @@ -205,6 +208,7 @@ "Plunge Rate": "침수율", "Policy Type": "정책 유형", "Poor entropy!": "엔트로피가 부족합니다!", + "Position plate": "플레이트 배치", "Power": "출력", "Press PAGE to cancel.": "PAGE로 취소.", "Press PAGE to toggle mode": "모드를 전환하려면 PAGE를 누르세요", @@ -348,6 +352,9 @@ "Word %d": "%d 단어", "Word Numbers": "단어 번호(1-2048)", "Words": "시드문구", + "Words 1-12": "단어 1-12", + "Words 13-24": "단어 13-24", + "Words 7-12": "단어 7-12", "XOR Result": "XOR 결과", "XOR With": "XOR With", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "다른 XOR 전류 니모닉? (패스프레이즈와 디스크립터가 폐기됩니다)", @@ -374,4 +381,4 @@ "to utf8": "utf8로", "unknown": "알 수 없음", "was NOT FOUND in the first %d addresses": "첫 번째 %d개의 주소에서 찾을 수 없습니다" -} \ No newline at end of file +} diff --git a/i18n/translations/nl-NL.json b/i18n/translations/nl-NL.json index 0595c0435..2c9de8da5 100644 --- a/i18n/translations/nl-NL.json +++ b/i18n/translations/nl-NL.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "Controleer of dit adres bij deze portemonnee hoort?", "Checked %d addresses with no matches.": "%d adressen gecontroleerd zonder overeenkomsten.", "Checking for SD card…": "Controleren op SD-kaart…", + "Click to read": "Klik om te lezen", "Confirm Tamper Check Code": "Bevestig de sabotagecontrolecode", "Convert Datum": "Datum converteren", "Could not determine change address.": "Kan adreswijziging niet bepalen.", @@ -113,6 +114,7 @@ "Flash Map": "Flash Map", "Flash Tools": "Flash Tools", "Flash filled with camera entropy": "Flash gevuld met camera-entropie", + "Flip plate": "Draai de plaat om", "Flute Diameter": "Fluit diameter", "Free:": "Vrij:", "From Storage": "Uit data-opslag", @@ -178,6 +180,7 @@ "Network": "Netwerk", "New Mnemonic": "Geheugensteun aanmaken", "New firmware detected.": "Nieuwe firmware gevonden.", + "Next": "Volgende", "No": "Nee", "No Passphrase": "Geen wachtwoordzin", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "Er zijn niet-ASCII-tekens gedetecteerd in uw wachtwoordzin. Krux kan niet garanderen dat andere portefeuilles dezelfde sleutel zullen afleiden.", @@ -205,6 +208,7 @@ "Plunge Rate": "Duik tarief", "Policy Type": "Type beleid", "Poor entropy!": "Slechte entropie!", + "Position plate": "Positioneer de plaat", "Power": "Vermogen", "Press PAGE to cancel.": "Druk op PAGE om te annuleren.", "Press PAGE to toggle mode": "PAGINA om de modus te wisselen", @@ -348,6 +352,9 @@ "Word %d": "Woord %d", "Word Numbers": "Woord nummers", "Words": "Woorden", + "Words 1-12": "Woorden 1-12", + "Words 13-24": "Woorden 13-24", + "Words 7-12": "Woorden 7-12", "XOR Result": "XOR-resultaat", "XOR With": "XOR met", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "XOR huidig geheugensteuntje met een ander? (passphrase en descriptor worden weggegooid)", @@ -374,4 +381,4 @@ "to utf8": "naar utf8", "unknown": "onbekend", "was NOT FOUND in the first %d addresses": "werd NIET GEVONDEN in de eerste %d adressen" -} \ No newline at end of file +} diff --git a/i18n/translations/pt-BR.json b/i18n/translations/pt-BR.json index e506f1ee2..89ff1a2ed 100644 --- a/i18n/translations/pt-BR.json +++ b/i18n/translations/pt-BR.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "Checar se o endereço pertence a esta carteira?", "Checked %d addresses with no matches.": "%d endereços checados sem correspondência.", "Checking for SD card…": "Procurando por cartão SD…", + "Click to read": "Clique para ler", "Confirm Tamper Check Code": "Confirmar código de verificação de integridade", "Convert Datum": "Converter dados", "Could not determine change address.": "Não foi possível determinar endereços de troco.", @@ -113,6 +114,7 @@ "Flash Map": "Mapa da Flash", "Flash Tools": "Ferramentas da Flash", "Flash filled with camera entropy": "Memória flash preenchida com entropia da câmera", + "Flip plate": "Vire a placa", "Flute Diameter": "Diâmetro da Fresa", "Free:": "Livre:", "From Storage": "Do armazenamento", @@ -178,6 +180,7 @@ "Network": "Rede", "New Mnemonic": "Novo Mnemônico", "New firmware detected.": "Novo firmware detectado.", + "Next": "Próximo", "No": "Não", "No Passphrase": "Sem Senha", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "Caracteres não ASCII foram detectados em sua senha. A Krux não pode garantir que outras carteiras obtenham a mesma chave.", @@ -205,6 +208,7 @@ "Plunge Rate": "Taxa de Mergulho", "Policy Type": "Tipo de Política", "Poor entropy!": "Entropia fraca!", + "Position plate": "Posicione a placa", "Power": "Potência", "Press PAGE to cancel.": "Pressione PAGE para cancelar.", "Press PAGE to toggle mode": "Pressione PAGE para alternar o modo", @@ -348,6 +352,9 @@ "Word %d": "Palavra %d", "Word Numbers": "Números das Palavras", "Words": "Palavras", + "Words 1-12": "Palavras 1-12", + "Words 13-24": "Palavras 13-24", + "Words 7-12": "Palavras 7-12", "XOR Result": "Resultado XOR", "XOR With": "XOR com", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "XOR mnemônico atual com outro? (senha e descritor serão descartados)", @@ -374,4 +381,4 @@ "to utf8": "para utf8", "unknown": "desconhecida", "was NOT FOUND in the first %d addresses": "NÃO FOI ENCONTRADO nos primeiros %d endereços" -} \ No newline at end of file +} diff --git a/i18n/translations/ru-RU.json b/i18n/translations/ru-RU.json index 4b47d0dd9..f18222b6e 100644 --- a/i18n/translations/ru-RU.json +++ b/i18n/translations/ru-RU.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "Проверить, что адрес принадлежит этому кошельку?", "Checked %d addresses with no matches.": "Проверено %d адресов без совпадений.", "Checking for SD card…": "Проверка SD-карты…", + "Click to read": "Нажмите для чтения", "Confirm Tamper Check Code": "Подтвердите код проверки вскрытия", "Convert Datum": "Преобразовать датум", "Could not determine change address.": "Не удалось определить адрес изменения.", @@ -113,6 +114,7 @@ "Flash Map": "Карта флэша", "Flash Tools": "Flash Tools", "Flash filled with camera entropy": "Флэш заполнен энтропией камеры", + "Flip plate": "Переверните пластину", "Flute Diameter": "Диаметр Флюта", "Free:": "Свободно:", "From Storage": "Из Памяти", @@ -178,6 +180,7 @@ "Network": "Сеть", "New Mnemonic": "Новая Мнемоника", "New firmware detected.": "Обнаружена новая прошивка.", + "Next": "Далее", "No": "Нет", "No Passphrase": "Нет Фраза-пароль", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "В вашей парольной фразе обнаружены символы, отличные от ASCII. Krux не может гарантировать, что другие кошельки получат тот же ключ.", @@ -205,6 +208,7 @@ "Plunge Rate": "Скорость Погружения", "Policy Type": "Тип политики", "Poor entropy!": "Плохая энтропия!", + "Position plate": "Расположите пластину", "Power": "Мощность", "Press PAGE to cancel.": "Нажмите PAGE, чтобы отменить.", "Press PAGE to toggle mode": "PAGE, чтобы переключить режим", @@ -348,6 +352,9 @@ "Word %d": "Слово %d", "Word Numbers": "Числа Слов", "Words": "Слова", + "Words 1-12": "Слова 1-12", + "Words 13-24": "Слова 13-24", + "Words 7-12": "Слова 7-12", "XOR Result": "Результат XOR", "XOR With": "XOR с", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "XOR current mnemonic with another one? (парольная фраза и дескриптор будут отброшены)", @@ -374,4 +381,4 @@ "to utf8": "to utf8", "unknown": "неизвестный", "was NOT FOUND in the first %d addresses": "нЕ НАЙДЕНО в первых %d адресах" -} \ No newline at end of file +} diff --git a/i18n/translations/tr-TR.json b/i18n/translations/tr-TR.json index 97747a70a..fcfddb2e9 100644 --- a/i18n/translations/tr-TR.json +++ b/i18n/translations/tr-TR.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "Bu adresin, bu cüzdana ait olduğunu kontrol et?", "Checked %d addresses with no matches.": "Eşleşmeyen %d adres kontrol edildi.", "Checking for SD card…": "SD kart kontrol ediliyor…", + "Click to read": "Okumak için tıklayın", "Confirm Tamper Check Code": "Kurcalama Kontrol Kodunu Onayla", "Convert Datum": "Veriyi Dönüştür", "Could not determine change address.": "Değişiklik adresi belirlenemedi.", @@ -113,6 +114,7 @@ "Flash Map": "Flash Haritası", "Flash Tools": "Flash Araçları", "Flash filled with camera entropy": "Flash kamera entropisi ile dolduruldu", + "Flip plate": "Plakayı çevirin", "Flute Diameter": "Flute Çapı", "Free:": "Boş:", "From Storage": "Depolamadan Seç", @@ -178,6 +180,7 @@ "Network": "Ağ", "New Mnemonic": "Yeni Mnemonic", "New firmware detected.": "Yeni donanım yazılımı tespit edildi.", + "Next": "İleri", "No": "Hayır", "No Passphrase": "Parola Yok", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "Parolanızda ASCII olmayan karakterler algılandı. Krux, diğer cüzdanların aynı anahtarı türeteceğini garanti edemez.", @@ -205,6 +208,7 @@ "Plunge Rate": "Dalış Hızı", "Policy Type": "Politika türü", "Poor entropy!": "Düşük entropi!", + "Position plate": "Plakayı konumlandırın", "Power": "Güç", "Press PAGE to cancel.": "İptal etmek için PAGE tuşuna basın.", "Press PAGE to toggle mode": "PAGE modunu değiştir", @@ -348,6 +352,9 @@ "Word %d": "Kelime %d", "Word Numbers": "Kelime Numaraları", "Words": "Kelimeler", + "Words 1-12": "Kelimeler 1-12", + "Words 13-24": "Kelimeler 13-24", + "Words 7-12": "Kelimeler 7-12", "XOR Result": "XOR Sonucu", "XOR With": "XOR ile", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "XOR başka biriyle geçerli anımsatıcı? (parola ve tanımlayıcı atılacak)", @@ -374,4 +381,4 @@ "to utf8": "utf8 'e", "unknown": "bilinmiyor", "was NOT FOUND in the first %d addresses": "ilk %d adreste BULUNAMADI" -} \ No newline at end of file +} diff --git a/i18n/translations/vi-VN.json b/i18n/translations/vi-VN.json index d901f57c9..8509c143a 100644 --- a/i18n/translations/vi-VN.json +++ b/i18n/translations/vi-VN.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "Kiểm tra địa chỉ đó có thuộc về ví này không?", "Checked %d addresses with no matches.": "Đã kiểm tra %d địa chỉ không khớp.", "Checking for SD card…": "Đang kiểm tra thẻ SD…", + "Click to read": "Nhấn để đọc", "Confirm Tamper Check Code": "Xác nhận mã kiểm tra giả mạo", "Convert Datum": "Chuyển đổi dữ liệu", "Could not determine change address.": "Không thể xác định địa chỉ thay đổi.", @@ -113,6 +114,7 @@ "Flash Map": "Bản đồ Flash", "Flash Tools": "Công cụ Flash", "Flash filled with camera entropy": "Đèn flash chứa đầy entropy của máy ảnh", + "Flip plate": "Lật tấm", "Flute Diameter": "Đường kính mũi cắt CNC", "Free:": "Khả dụng:", "From Storage": "Từ bộ lưu trữ", @@ -178,6 +180,7 @@ "Network": "Mạng lưới", "New Mnemonic": "Mnemonic mới", "New firmware detected.": "Phát hiện phần sụn mới.", + "Next": "Tiếp", "No": "Không", "No Passphrase": "Không có cụm mật khẩu", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "Các ký tự không phải ASCII đã được phát hiện trong cụm mật khẩu của bạn. Krux không thể đảm bảo rằng các ví khác sẽ lấy được cùng một khóa.", @@ -205,6 +208,7 @@ "Plunge Rate": "Tỷ lệ sụt giảm", "Policy Type": "Loại Chính sách", "Poor entropy!": "Entropy kém!", + "Position plate": "Đặt tấm", "Power": "Công suất", "Press PAGE to cancel.": "Nhấn PAGE để hủy.", "Press PAGE to toggle mode": "PAGE để chuyển chế độ", @@ -348,6 +352,9 @@ "Word %d": "Kí tự %d", "Word Numbers": "Từ số", "Words": "Từ ngữ", + "Words 1-12": "Từ 1-12", + "Words 13-24": "Từ 13-24", + "Words 7-12": "Từ 7-12", "XOR Result": "Kết quả XOR", "XOR With": "XOR với", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "Ghi nhớ hiện tại của XOR với một cụm từ khác? (cụm mật khẩu và mô tả sẽ bị loại bỏ)", @@ -374,4 +381,4 @@ "to utf8": "đến utf8", "unknown": "không rõ", "was NOT FOUND in the first %d addresses": "kHÔNG TÌM THẤY trong %d địa chỉ đầu tiên" -} \ No newline at end of file +} diff --git a/i18n/translations/zh-CN.json b/i18n/translations/zh-CN.json index e6deb371e..5b5a1bdc2 100644 --- a/i18n/translations/zh-CN.json +++ b/i18n/translations/zh-CN.json @@ -45,6 +45,7 @@ "Check that address belongs to this wallet?": "检查该地址是否属于此钱包?", "Checked %d addresses with no matches.": "已检查 %d 个不匹配的地址.", "Checking for SD card…": "检查卡…", + "Click to read": "点击读取", "Confirm Tamper Check Code": "确认防篡改检查码", "Convert Datum": "转换基准", "Could not determine change address.": "无法确定更改地址.", @@ -113,6 +114,7 @@ "Flash Map": "Flash地图", "Flash Tools": "Flash工具", "Flash filled with camera entropy": "Flash已用摄像头熵填充", + "Flip plate": "翻转板", "Flute Diameter": "刀具直径", "Free:": "空闲:", "From Storage": "从存储中", @@ -178,6 +180,7 @@ "Network": "网络", "New Mnemonic": "新助记词", "New firmware detected.": "检测到新固件.", + "Next": "下一步", "No": "否", "No Passphrase": "无 Passphrase ", "Non-ASCII characters were detected in your passphrase. Krux cannot guarantee that other wallets will derive the same key.": "在您的密码短语中检测到非ASCII字符.Krux无法保证其他钱包将获得相同的密钥.", @@ -205,6 +208,7 @@ "Plunge Rate": "下刀速度", "Policy Type": "政策类型", "Poor entropy!": "熵值低!", + "Position plate": "放置板", "Power": "功率", "Press PAGE to cancel.": "按PAGE取消.", "Press PAGE to toggle mode": "按 PAGE 切换模式", @@ -348,6 +352,9 @@ "Word %d": "词 %d", "Word Numbers": "单词序号", "Words": "单词", + "Words 1-12": "单词 1-12", + "Words 13-24": "单词 13-24", + "Words 7-12": "单词 7-12", "XOR Result": "XOR结果", "XOR With": "XOR With", "XOR current mnemonic with another one? (passphrase and descriptor will be discarded)": "XOR当前助记符与其他助记符? (密码短语和描述符将被丢弃)", @@ -374,4 +381,4 @@ "to utf8": "to_utf8()", "unknown": "未知", "was NOT FOUND in the first %d addresses": "在前 %d 个地址中未找到" -} \ No newline at end of file +} diff --git a/src/krux/pages/mnemonic_loader.py b/src/krux/pages/mnemonic_loader.py index 366b5af9d..ca6adf707 100644 --- a/src/krux/pages/mnemonic_loader.py +++ b/src/krux/pages/mnemonic_loader.py @@ -79,6 +79,7 @@ def load_key_from_camera(self): t("Binary Grid"), lambda: self.load_key_from_tiny_seed_image("Binary Grid"), ), + ("Stackbit 1248", self.load_key_from_1248_scan), ], ) index, status = submenu.run_loop() @@ -266,6 +267,29 @@ def load_key_from_1248(self): return self._load_key_from_words(words) return MENU_CONTINUE + def load_key_from_1248_scan(self): + """Menu handler to scan key from Stackbit 1248 plate with camera""" + from .stack_1248_scanner import StackbitScanner + + len_mnemonic = self.choose_len_mnemonic() + if not len_mnemonic: + return MENU_CONTINUE + + intro = t("Paint punched dots black so they can be detected.") + " " + intro += t("Use a black background surface.") + " " + intro += t("Align camera and backup plate properly.") + self.ctx.display.draw_hcentered_text(intro) + if not self.prompt(t("Proceed?"), BOTTOM_PROMPT_LINE): + return MENU_CONTINUE + + stackbit_scanner = StackbitScanner(self.ctx) + words = stackbit_scanner.scanner(len_mnemonic == 24) + del stackbit_scanner + if words is None: + self.flash_error(t("Failed to load")) + return MENU_CONTINUE + return self._load_key_from_words(words) + def load_key_from_tiny_seed(self): """Menu handler to manually load key from Tinyseed sheet metal storage method""" from .tiny_seed import TinySeed diff --git a/src/krux/pages/stack_1248_scanner.py b/src/krux/pages/stack_1248_scanner.py new file mode 100644 index 000000000..b2bbd2782 --- /dev/null +++ b/src/krux/pages/stack_1248_scanner.py @@ -0,0 +1,936 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021-2024 Krux contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import lcd +import sensor +import time +from embit.wordlists.bip39 import WORDLIST +from . import Page +from ..krux_settings import t +from ..themes import theme +from ..display import DEFAULT_PADDING, FONT_HEIGHT, FONT_WIDTH +from ..camera import BINARY_GRID_MODE +from ..wdt import wdt +from ..input import ( + BUTTON_ENTER, + BUTTON_PAGE, + BUTTON_PAGE_PREV, + BUTTON_TOUCH, + FAST_FORWARD, + FAST_BACKWARD, +) +from ..kboard import kboard +from .stack_1248 import Stackbit, STACKBIT_GO_INDEX, STACKBIT_ESC_INDEX + + +class StackbitScanner(Page): + """Uses camera sensor to detect punch pattern on Stackbit 1248 metal plate + + Stackbit 1248 is a metal backup plate for BIP39 seed phrases using + binary-coded decimal (1-2-4-8) encoding. Each word is represented as + a 4-digit number (0001-2048). + + Plate specifications: + - Full plate: 85mm x 54mm (aspect ratio = 1.574), 16x12 grid, 12 words + - Mini plate: 42.5mm x 54mm (aspect ratio = 0.787), 8x12 grid, 6 words + + Grid layout: + - Columns 0 (and 8 on full): Word index numbers (skipped during reading) + - Columns 1 (and 9 on full): Milhar digit (1 or 2) + - Columns 2-7 (and 10-15 on full): Three pairs of 1-2-4-8 encoding + """ + + # Plate type constants + PLATE_FULL = "full" # 85x54mm, 16x12 grid, 12 words + PLATE_MINI = "mini" # 42.5x54mm, 8x12 grid, 6 words + + def __init__(self, ctx): + super().__init__(ctx, None) + self.ctx = ctx + self.x_regions = [] + self.y_regions = [] + self.blob_otsu = 0x80 + self.debug_mode = True # Show visual markers for detected punches + self.plate_type = self.PLATE_FULL # Detected plate type + + # pylint: disable=R0914 + def _detect_plate(self, img, force_type=None): + """Detect the Stackbit 1248 plate as a bright blob + + Args: + force_type: If set, only detect plates of this type (PLATE_FULL or PLATE_MINI) + + Returns tuple: (rect, plate_type) or (None, None) + """ + try: + self.blob_otsu = img.get_histogram().get_threshold().value() + except: + pass + + # Use threshold that separates bright plate from dark background + blob_threshold = [(max(self.blob_otsu - 20, 50), 255)] + + # Use smaller strides for more accurate edge detection + blobs = img.find_blobs( + blob_threshold, + x_stride=5, + y_stride=5, + area_threshold=2000, + pixels_threshold=1500, + merge=True, + ) + + best_rect = None + best_score = 0 + best_type = None + + # Aspect ratios for different plate types + # Full plate: 85 / 54 = 1.574 (landscape) + # Mini plate: 42.5 / 54 = 0.787 (portrait) + full_aspect = 1.574 + mini_aspect = 0.787 + aspect_tolerance = 0.35 + + img_center_x = img.width() // 2 + img_center_y = img.height() // 2 + + for blob in blobs: + rect = blob.rect() + if rect[3] == 0 or rect[2] == 0: + continue + + aspect = rect[2] / rect[3] + + # Check which plate type matches + plate_type = None + aspect_diff = 999 + + if force_type == self.PLATE_FULL or force_type is None: + if abs(aspect - full_aspect) < aspect_tolerance: + plate_type = self.PLATE_FULL + aspect_diff = abs(aspect - full_aspect) + + if force_type == self.PLATE_MINI or force_type is None: + mini_diff = abs(aspect - mini_aspect) + if mini_diff < aspect_tolerance and mini_diff < aspect_diff: + plate_type = self.PLATE_MINI + aspect_diff = mini_diff + + if plate_type is None: + continue + + # Score based on aspect ratio match + aspect_score = 1.0 / (1.0 + aspect_diff * 3) + + # Score based on area + area_score = min(1.0, blob.area() / 15000) + + # Score based on centering + blob_center_x = rect[0] + rect[2] // 2 + blob_center_y = rect[1] + rect[3] // 2 + dist_from_center = ( + (blob_center_x - img_center_x) ** 2 + + (blob_center_y - img_center_y) ** 2 + ) ** 0.5 + max_dist = (img_center_x**2 + img_center_y**2) ** 0.5 + center_score = 1.0 - (dist_from_center / max_dist) + + # Combined score + score = aspect_score * 0.5 + area_score * 0.35 + center_score * 0.15 + + if score > best_score: + best_score = score + best_rect = rect + best_type = plate_type + + if best_rect: + self.plate_type = best_type + + return best_rect, best_type + + def _create_grid_over_rect(self, rect, plate_type=None): + """Create grid regions over detected rectangle + + Args: + plate_type: PLATE_FULL (16x12) or PLATE_MINI (8x12) + """ + self.x_regions = [] + self.y_regions = [] + + if plate_type is None: + plate_type = self.plate_type + + x, y, w, h = rect + + # Number of columns depends on plate type + num_cols = 16 if plate_type == self.PLATE_FULL else 8 + col_width = w / num_cols + for i in range(num_cols + 1): + self.x_regions.append(int(x + i * col_width)) + + # 12 rows for both plate types + row_height = h / 12 + for i in range(13): + self.y_regions.append(int(y + i * row_height)) + + def _draw_grid(self, img, rect, plate_type=None): + """Draw grid overlay on detected rectangle""" + if plate_type is None: + plate_type = self.plate_type + + # Draw rectangle outline + img.draw_rectangle(rect, lcd.WHITE, thickness=2) + + # Draw vertical lines + for i, x in enumerate(self.x_regions): + # Thicker lines for word separators + if plate_type == self.PLATE_FULL: + thickness = 2 if i in (0, 8) else 1 + else: + thickness = 2 if i == 0 else 1 + img.draw_line( + x, rect[1], x, rect[1] + rect[3], lcd.WHITE, thickness=thickness + ) + + # Draw horizontal lines (12 rows) + for y in self.y_regions: + img.draw_line(rect[0], y, rect[0] + rect[2], y, lcd.WHITE, thickness=1) + + def _read_cell(self, img, x, y, w, h, draw_debug=False): + """Read a single cell and return True if punched + + Uses multiple detection methods: + 1. Adaptive threshold (luminance-based) + 2. Circular blob detection (shape-based) + 3. Contrast detection (dark vs light) + """ + if x < 0 or y < 0 or w <= 0 or h <= 0: + return False + if x + w > img.width() or y + h > img.height(): + return False + + try: + stats = img.get_statistics(roi=(x, y, w, h)) + cell_lum = stats.l_mean() + + is_punched = False + + # Method 1: Adaptive threshold + relative_threshold = self.blob_otsu - 30 + if cell_lum < relative_threshold: + is_punched = True + + # Method 2: Circular blob detection + if not is_punched: + try: + dark_threshold = max(20, cell_lum - 40) + blob_threshold = [(0, dark_threshold)] + blobs = img.find_blobs( + blob_threshold, + roi=(x, y, w, h), + pixels_threshold=int(w * h * 0.1), + area_threshold=int(w * h * 0.08), + merge=True, + ) + for blob in blobs: + if blob.roundness() > 0.3: + is_punched = True + break + except: + pass + + # Method 3: High contrast detection + if not is_punched: + try: + std = stats.l_stdev() + if std > 25: + is_punched = True + except: + pass + + # Draw debug marker if requested + if draw_debug and is_punched: + img.draw_rectangle((x, y, w, h), lcd.BLACK, thickness=2) + + return is_punched + except: + return False + + def _create_grid_regions(self, rect, plate_type=None): + """Create grid over the detected plate + + Args: + plate_type: PLATE_FULL (16x12) or PLATE_MINI (8x12) + + Returns: (x_regions, y_regions) + """ + if plate_type is None: + plate_type = self.plate_type + + x_regions = [] + y_regions = [] + + num_cols = 16 if plate_type == self.PLATE_FULL else 8 + + x_step = rect[2] / num_cols + for i in range(num_cols + 1): + x_regions.append(int(rect[0] + i * x_step)) + + y_step = rect[3] / 12 + for i in range(13): + y_regions.append(int(rect[1] + i * y_step)) + + return x_regions, y_regions + + def _read_all_grid_cells(self, img, rect, plate_type=None): + """Read all grid cells and return a 2D boolean array + + Args: + plate_type: PLATE_FULL (16x12) or PLATE_MINI (8x12) + + Returns: (grid, x_regions, y_regions) + """ + if plate_type is None: + plate_type = self.plate_type + + x_regions, y_regions = self._create_grid_regions(rect, plate_type) + num_cols = 16 if plate_type == self.PLATE_FULL else 8 + grid = [] + + for row_idx in range(12): + row = [] + y = y_regions[row_idx] + h = y_regions[row_idx + 1] - y + + # Use centered sampling (70% width, 60% height) + sample_h = int(h * 0.6) + sample_y = y + int(h * 0.2) + + for col_idx in range(num_cols): + x = x_regions[col_idx] + w = x_regions[col_idx + 1] - x + sample_w = int(w * 0.7) + sample_x = x + int(w * 0.15) + + is_punched = self._read_cell( + img, sample_x, sample_y, sample_w, sample_h, draw_debug=False + ) + row.append(is_punched) + + grid.append(row) + + # Filter rounded corner cells (3mm radius causes false detections) + # Affects the 4 physical corners of the plate + last_col = num_cols - 1 + grid[0][0] = False + grid[0][last_col] = False + grid[11][0] = False + grid[11][last_col] = False + + return grid, x_regions, y_regions + + def _decode_6_words_from_half(self, grid, col_offset=0): + """Decode 6 word numbers from one half of the grid + + Args: + grid: 2D boolean array of punched cells + col_offset: 0 for left half (cols 0-7), 8 for right half (cols 8-15) + + Returns list of 6 integers (word numbers 1-2048) + """ + numbers = [] + + for word_idx in range(6): + row_upper = word_idx * 2 + row_lower = word_idx * 2 + 1 + + # Column 1 (or 9): Milhar (skip col 0/8 indexer) + milhar_col = col_offset + 1 + milhar = 0 + if grid[row_upper][milhar_col]: + milhar = 1 + elif grid[row_lower][milhar_col]: + milhar = 2 + + # Columns 2-7 (or 10-15): Three pairs of 1-2-4-8 encoding + digits = [milhar] + for pair_idx in range(3): + col_left = col_offset + 2 + pair_idx * 2 + col_right = col_offset + 3 + pair_idx * 2 + + # Left column: upper=1, lower=4 + # Right column: upper=2, lower=8 + val_1 = 1 if grid[row_upper][col_left] else 0 + val_4 = 4 if grid[row_lower][col_left] else 0 + val_2 = 2 if grid[row_upper][col_right] else 0 + val_8 = 8 if grid[row_lower][col_right] else 0 + + digit = val_1 + val_2 + val_4 + val_8 + digits.append(digit) + + number = digits[0] * 1000 + digits[1] * 100 + digits[2] * 10 + digits[3] + numbers.append(number) + + return numbers + + def _decode_numbers_from_grid(self, grid, plate_type=None): + """Decode word numbers from the grid + + Args: + plate_type: PLATE_FULL (12 words) or PLATE_MINI (6 words) + + Returns list of integers (word numbers 1-2048) + """ + if plate_type is None: + plate_type = self.plate_type + + if plate_type == self.PLATE_MINI: + # Mini plate: 8 columns, 6 words (same layout as left half) + return self._decode_6_words_from_half(grid, col_offset=0) + # Full plate: 16 columns, 12 words + numbers = [] + # Left half (words 1-6, columns 0-7) + numbers.extend(self._decode_6_words_from_half(grid, col_offset=0)) + # Right half (words 7-12, columns 8-15) + numbers.extend(self._decode_6_words_from_half(grid, col_offset=8)) + return numbers + + # pylint: disable=W0212 + def _edit_single_word(self, word_index, number): + """Edit a single word using the Stackbit 1248 editor UI + + Reuses the existing Stackbit class for proven input handling. + + Args: + word_index: Display index of the word (1-based) + number: Current word number (1-2048) + + Returns: + New word number if confirmed (Go), or None if cancelled (Esc) + """ + sb = Stackbit(self.ctx) + + # Setup editor layout (same as enter_1248) + if not kboard.is_m5stickv: + sb.x_pad = 3 * FONT_WIDTH + else: + sb.x_pad = 2 * FONT_WIDTH + sb.x_offset = self.ctx.display.width() - 8 * sb.x_pad + sb.x_offset = max(sb.x_offset, DEFAULT_PADDING) // 2 + sb.y_offset = 3 * FONT_HEIGHT + sb.y_pad = 2 * FONT_HEIGHT + + index = 0 + # Convert number to digits + num_str = "{:04d}".format(number) + digits = [int(c) for c in num_str] + + self.ctx.display.clear() + while True: + sb._map_keys_array() + self.ctx.display.draw_hcentered_text("Edit Word " + str(word_index)) + y_offset = sb.y_offset + sb._draw_grid(y_offset) + sb._draw_labels(y_offset, word_index) + sb._draw_menu() + if self.ctx.input.buttons_active: + sb._draw_index(index) + sb.preview_word(digits) + sb._draw_punched(digits, y_offset) + + btn = self.ctx.input.wait_for_fastnav_button() + if btn == BUTTON_TOUCH: + btn = BUTTON_ENTER + index = self.ctx.input.touch.current_index() + if btn == BUTTON_ENTER: + if index >= STACKBIT_GO_INDEX: + word = sb.digits_to_word(digits) + if word is not None: + return ( + digits[0] * 1000 + + digits[1] * 100 + + digits[2] * 10 + + digits[3] + ) + elif index >= STACKBIT_ESC_INDEX: + return None + elif index < 14: + digits = sb._toggle_bit(digits, index) + else: + index = sb.index(index, btn) + self.ctx.display.clear() + + # pylint: disable=R0914,R0912,R0915 + def _show_stackbit_words(self, grid, word_offset=0, plate_type=None): + """Show decoded words with editing support and Back/Next navigation + + Touch a word to edit it with the 1248 editor. + Back/Next buttons at footer to navigate pages. + + Args: + word_offset: Starting word index offset + plate_type: PLATE_FULL or PLATE_MINI + + Returns: + List of word numbers (possibly edited) or None if cancelled + """ + if plate_type is None: + plate_type = self.plate_type + + numbers = self._decode_numbers_from_grid(grid, plate_type) + + x_offset = DEFAULT_PADDING + x_pad = 2 * FONT_WIDTH + y_pad = FONT_HEIGHT + row_spacing = 6 + row_height = 2 * y_pad + row_spacing + + def draw_word_row(word_idx, number, y_offset): + """Draw one word row with Stackbit 1248 visual representation""" + digits = [ + (number // 1000) % 10, + (number // 100) % 10, + (number // 10) % 10, + number % 10, + ] + digits_str = "{:04d}".format(number) + + if 1 <= number <= 2048: + word = WORDLIST[number - 1] + else: + word = "????" + + grid_x_offset = x_offset - FONT_WIDTH // 2 + index_x_offset = x_offset + x_pad // 2 - 1 + if len(str(word_idx)) > 1: + index_x_offset -= FONT_WIDTH + + self.ctx.display.fill_rectangle( + grid_x_offset, + y_offset - 2, + x_pad + FONT_WIDTH // 2, + 2 * y_pad + 2, + theme.disabled_color, + ) + self.ctx.display.draw_string( + index_x_offset, + y_offset + y_pad // 2, + str(word_idx), + theme.fg_color, + theme.disabled_color, + ) + + numbers_offset = x_offset + x_pad + (x_pad - FONT_WIDTH) // 2 + upper_numbers = [1, 1, 2, 1, 2, 1, 2] + lower_numbers = [2, 4, 8, 4, 8, 4, 8] + label_y_offset = y_offset + (y_pad - FONT_HEIGHT) // 2 + for i in range(len(upper_numbers)): + self.ctx.display.draw_string( + numbers_offset, + label_y_offset, + str(upper_numbers[i]), + theme.fg_color, + ) + self.ctx.display.draw_string( + numbers_offset, + label_y_offset + y_pad, + str(lower_numbers[i]), + theme.fg_color, + ) + numbers_offset += x_pad + + width = 8 * x_pad + FONT_WIDTH // 2 + height = 2 * y_pad + 2 + self.ctx.display.draw_line( + grid_x_offset, + y_offset - 2, + grid_x_offset + width, + y_offset - 2, + theme.frame_color, + ) + self.ctx.display.draw_line( + grid_x_offset, + y_offset - 2 + height, + grid_x_offset + width, + y_offset - 2 + height, + theme.frame_color, + ) + x_bar = x_offset + self.ctx.display.draw_line( + grid_x_offset, + y_offset - 2, + grid_x_offset, + y_offset - 2 + height, + theme.frame_color, + ) + x_bar += x_pad + self.ctx.display.draw_line( + x_bar, y_offset - 2, x_bar, y_offset - 2 + height, theme.frame_color + ) + x_bar += x_pad + for _ in range(4): + self.ctx.display.draw_line( + x_bar, y_offset - 2, x_bar, y_offset - 2 + height, theme.frame_color + ) + x_bar += 2 * x_pad + + outline_width = x_pad - 6 + outline_height = y_pad - 4 + outline_x = x_offset + x_pad + 3 + if digits[0] == 2: + self.ctx.display.outline( + outline_x, + y_offset + y_pad + 1, + outline_width, + outline_height, + theme.highlight_color, + ) + elif digits[0] == 1: + self.ctx.display.outline( + outline_x, + y_offset + 1, + outline_width, + outline_height, + theme.highlight_color, + ) + outline_x += x_pad + for d in range(3): + digit = digits[d + 1] + if (digit >> 3) & 1: + self.ctx.display.outline( + outline_x + x_pad, + y_offset + y_pad + 1, + outline_width, + outline_height, + theme.highlight_color, + ) + if (digit >> 2) & 1: + self.ctx.display.outline( + outline_x, + y_offset + y_pad + 1, + outline_width, + outline_height, + theme.highlight_color, + ) + if (digit >> 1) & 1: + self.ctx.display.outline( + outline_x + x_pad, + y_offset + 1, + outline_width, + outline_height, + theme.highlight_color, + ) + if digit & 1: + self.ctx.display.outline( + outline_x, + y_offset + 1, + outline_width, + outline_height, + theme.highlight_color, + ) + outline_x += 2 * x_pad + + self.ctx.display.draw_string( + x_offset + 17 * FONT_WIDTH, y_offset, digits_str, theme.highlight_color + ) + self.ctx.display.draw_string( + x_offset + 17 * FONT_WIDTH, y_offset + y_pad, word, theme.disabled_color + ) + + # Split numbers into pages of 6 + num_words = len(numbers) + if num_words <= 6: + title = "Stackbit 1248 Mini" + pages = [list(numbers)] + page_offsets = [word_offset] + else: + title = "Stackbit 1248" + pages = [list(numbers[:6]), list(numbers[6:])] + page_offsets = [word_offset, word_offset + 6] + + current_page = 0 + + while True: + is_first = current_page == 0 + is_last = current_page == len(pages) - 1 + page_nums = pages[current_page] + num_on_page = len(page_nums) + + # Draw page + self.ctx.display.clear() + self.ctx.display.draw_hcentered_text(title) + + y_positions = [] + y_pos = 2 * FONT_HEIGHT + for i in range(num_on_page): + draw_word_row(page_offsets[current_page] + i + 1, page_nums[i], y_pos) + y_positions.append(y_pos) + y_pos += row_height + + # Draw footer: Back / Next (or Go) + footer_y = self.ctx.display.height() - FONT_HEIGHT - 4 + if not is_first: + self.ctx.display.draw_string( + x_offset, footer_y, "< " + t("Back"), theme.no_esc_color + ) + next_label = t("Next") + " >" if not is_last else t("Go") + " >" + next_color = theme.fg_color if not is_last else theme.go_color + next_x = self.ctx.display.width() - len(next_label) * FONT_WIDTH - x_offset + self.ctx.display.draw_string(next_x, footer_y, next_label, next_color) + + # Setup touch regions: 2 columns x (num_on_page + 1) rows + # Indices: 0..2*num_on_page-1 = word rows, 2*num_on_page = Back, 2*num_on_page+1 = Next + if kboard.has_touchscreen: + self.ctx.input.touch.clear_regions() + mid_x = self.ctx.display.width() // 2 + self.ctx.input.touch.x_regions.append(0) + self.ctx.input.touch.x_regions.append(mid_x) + self.ctx.input.touch.x_regions.append(self.ctx.display.width()) + for yp in y_positions: + self.ctx.input.touch.y_regions.append(yp - 2) + self.ctx.input.touch.y_regions.append(footer_y - 4) + self.ctx.input.touch.y_regions.append(self.ctx.display.height()) + + # Wait for input + btn = self.ctx.input.wait_for_fastnav_button() + + if btn == BUTTON_TOUCH: + idx = self.ctx.input.touch.current_index() + row = idx // 2 + col = idx % 2 + + if row < num_on_page: + # Touch on word row → edit it + edited = self._edit_single_word( + page_offsets[current_page] + row + 1, page_nums[row] + ) + if edited is not None: + pages[current_page][row] = edited + # Redraw page (continue loop) + elif row == num_on_page: + # Footer row + if col == 0 and not is_first: + current_page -= 1 + else: + if is_last: + result = [] + for p in pages: + result.extend(p) + return result + current_page += 1 + elif btn == BUTTON_ENTER: + if is_last: + result = [] + for p in pages: + result.extend(p) + return result + current_page += 1 + elif btn in (BUTTON_PAGE, FAST_FORWARD): + if is_last: + result = [] + for p in pages: + result.extend(p) + return result + current_page += 1 + elif btn in (BUTTON_PAGE_PREV, FAST_BACKWARD): + if not is_first: + current_page -= 1 + + def _numbers_to_words(self, numbers): + """Convert list of word numbers to BIP39 words + + Returns list of words if all valid, None otherwise + """ + words = [] + for number in numbers: + if 1 <= number <= 2048: + words.append(WORDLIST[number - 1]) + else: + return None + return words + + # pylint: disable=R0914,R0912,R0915 + def scanner(self, w24=False): + """Scans the Stackbit 1248 plate with manual trigger + + Automatically detects plate type: + - Full plate (85x54mm): 12 words at once + - Mini plate (42.5x54mm): 6 words, requires 2 scans for 12 words + + Args: + w24: If True, scans for 24 words (front + back of full plate) + + Returns: + List of 12 or 24 BIP39 words if successfully read, None otherwise + """ + page = 0 # 0 = first scan, 1 = second scan (for mini or 24-word mode) + all_words = [] + detected_plate_type = None # Will be set on first successful detection + mini_mode = False # True if using mini plate for 12 words + + self.ctx.display.clear() + if w24: + message = ( + t("Position plate") + "\n" + t("Words 1-12") + "\n" + t("Click to read") + ) + else: + message = t("Position plate") + "\n" + t("Click to read") + self.ctx.display.draw_centered_text(message) + time.sleep(2) + + self.ctx.camera.initialize_run(mode=BINARY_GRID_MODE) + self.ctx.camera.zoom_mode() + self.ctx.display.to_landscape() + self.ctx.display.clear() + + while True: + wdt.feed() + img = self.ctx.camera.snapshot() + + # Detect plate - for 24-word mode, force full plate detection + force_type = self.PLATE_FULL if w24 else None + rect, plate_type = self._detect_plate(img, force_type) + + if rect and plate_type: + self._create_grid_over_rect(rect, plate_type) + self._draw_grid(img, rect, plate_type) + + # Mark detected punches in real-time + num_cols = 16 if plate_type == self.PLATE_FULL else 8 + for row_idx in range(12): + y = self.y_regions[row_idx] + h = self.y_regions[row_idx + 1] - y + sample_h = int(h * 0.6) + sample_y = y + int(h * 0.2) + + for col_idx in range(num_cols): + # Skip indexer columns + if plate_type == self.PLATE_FULL and col_idx in (0, 8): + continue + if plate_type == self.PLATE_MINI and col_idx == 0: + continue + + x = self.x_regions[col_idx] + w = self.x_regions[col_idx + 1] - x + sample_w = int(w * 0.7) + sample_x = x + int(w * 0.15) + + self._read_cell( + img, sample_x, sample_y, sample_w, sample_h, True + ) + + # Display image + lcd.display(img) + + # Check for click/touch to perform final reading + if self.ctx.input.enter_event() or self.ctx.input.touch_event( + validate_position=False + ): + if rect and plate_type: + sensor.run(0) + self.ctx.display.to_portrait() + + # Remember plate type from first scan + if detected_plate_type is None: + detected_plate_type = plate_type + mini_mode = plate_type == self.PLATE_MINI and not w24 + + grid, _, _ = self._read_all_grid_cells(img, rect, plate_type) + + # Calculate word offset for display + if w24 and page == 1: + word_offset = 12 # Second scan of 24-word mode: words 13-24 + elif mini_mode and page == 1: + word_offset = 6 # Second scan of mini plate: words 7-12 + else: + word_offset = 0 # First scan: words 1-6 or 1-12 + + edited_numbers = self._show_stackbit_words( + grid, word_offset, plate_type + ) + + if edited_numbers is not None: + words = self._numbers_to_words(edited_numbers) + else: + words = None + + if words: + if w24: + # 24-word mode: always uses full plate, 2 scans + if page == 0: + all_words = words[:] + page = 1 + + self.ctx.display.clear() + self.flash_text( + t("Flip plate") + + "\n" + + t("Words 13-24") + + "\n\n" + + t("Click to read") + ) + + self.ctx.camera.initialize_run(mode=BINARY_GRID_MODE) + self.ctx.camera.zoom_mode() + self.ctx.display.to_landscape() + self.ctx.display.clear() + continue + all_words.extend(words) + return all_words + if mini_mode: + # Mini plate 12-word mode: 2 scans of 6 words each + if page == 0: + all_words = words[:] + page = 1 + + self.ctx.display.clear() + self.flash_text( + t("Flip plate") + + "\n" + + t("Words 7-12") + + "\n\n" + + t("Click to read") + ) + + self.ctx.camera.initialize_run(mode=BINARY_GRID_MODE) + self.ctx.camera.zoom_mode() + self.ctx.display.to_landscape() + self.ctx.display.clear() + continue + all_words.extend(words) + return all_words + # Full plate 12-word mode: single scan + return words + + # Invalid words - return to scanning + self.ctx.display.to_landscape() + self.ctx.camera.initialize_run(mode=BINARY_GRID_MODE) + self.ctx.camera.zoom_mode() + continue + + # Check for exit + if self.ctx.input.page_event() or self.ctx.input.page_prev_event(): + break + + sensor.run(0) + self.ctx.display.to_portrait() + return None diff --git a/src/krux/translations/__init__.py b/src/krux/translations/__init__.py index 82562adf9..455c45fca 100644 --- a/src/krux/translations/__init__.py +++ b/src/krux/translations/__init__.py @@ -79,6 +79,7 @@ 3119547911, 1187826970, 4011811253, + 2369062983, 422237057, 1464900930, 3625040530, @@ -147,6 +148,7 @@ 828589330, 2777318640, 1384661010, + 1503994858, 1406590538, 1077771640, 1893243331, @@ -212,6 +214,7 @@ 2939797024, 73574491, 3634967631, + 2753412866, 4063104189, 2325721074, 2474845199, @@ -239,6 +242,7 @@ 784609464, 473757803, 978980745, + 3394171978, 1783312036, 3113361245, 4099367945, @@ -382,6 +386,9 @@ 797660533, 3742424146, 2965123464, + 2431322375, + 3367509802, + 3045086939, 903400125, 3368232542, 3520459693, diff --git a/src/krux/translations/de.py b/src/krux/translations/de.py index 61c045592..0593531f0 100644 --- a/src/krux/translations/de.py +++ b/src/krux/translations/de.py @@ -67,6 +67,7 @@ "Überprüfen, ob diese Adresse zu dieser Wallet gehört?", "Überprüfte %d Adresse ohne Übereinstimmungen.", "SD-Karte wird gesucht…", + "Klicken zum Lesen", "Bestätigen Sie den Tamper Check Code", "Datum konvertieren", "Änderungsadresse konnte nicht ermittelt werden.", @@ -135,6 +136,7 @@ "Flash-Karte", "Flash-Tools", "Flash gefüllt mit Kameraentropie", + "Platte umdrehen", "Flötendurchmesser", "Frei:", "Vom Speicher", @@ -200,6 +202,7 @@ "Netzwerk", "Neue Mnemonic", "Neue Firmware erkannt.", + "Weiter", "Nein", "Keine Passphrase", "Nicht-ASCII-Zeichen wurden in Ihrer Passphrase erkannt. Krux kann nicht garantieren, dass andere Wallets den gleichen Schlüssel ableiten.", @@ -227,6 +230,7 @@ "Tauchrate", "Richtlinientyp", "Schlechte Entropie!", + "Platte positionieren", "Leistung", "PAGE zum Abbrechen drücken.", "PAGE für Moduswechsel", @@ -370,6 +374,9 @@ "Wort %d", "Wortnummern", "Wörter", + "Wörter 1-12", + "Wörter 13-24", + "Wörter 7-12", "XOR-Ergebnis", "XOR mit", "XOR-Strommnemotechnik mit einer anderen? (Passphrase und Deskriptor werden verworfen)", diff --git a/src/krux/translations/es.py b/src/krux/translations/es.py index 865eb4a2e..e7f105c84 100644 --- a/src/krux/translations/es.py +++ b/src/krux/translations/es.py @@ -67,6 +67,7 @@ "¿Verificar que la dirección pertenece a esta cartera?", "Comprobado %d direcciones sin coincidencias.", "Buscando tarjeta SD…", + "Haga clic para leer", "Confirmar el código de verificación", "Convertir dato", "No se pudo determinar la dirección de cambio.", @@ -135,6 +136,7 @@ "Mapa Flash", "Herramientas Flash", "Flash lleno de entropía de cámara", + "Voltee la placa", "Diámetro de la Flauta", "Libre:", "Desde el Almacenamiento", @@ -200,6 +202,7 @@ "Red", "Nuevo Mnemónico", "Nuevo firmware detectado.", + "Siguiente", "No", "Sin Passphrase", "Se detectaron caracteres no ASCII en su frase de contraseña. Krux no puede garantizar que otros monederos obtengan la misma clave.", @@ -227,6 +230,7 @@ "Tasa de Caída", "Tipo de póliza", "Baja entropía!", + "Posicione la placa", "Potencia", "Pulse PAGE para cancelar.", "PAGE para cambiar el modo", @@ -370,6 +374,9 @@ "Palabra %d", "Números de Palabra", "Palabras", + "Palabras 1-12", + "Palabras 13-24", + "Palabras 7-12", "Resultado XOR", "XOR Con", "¿XOR mnemónico actual con otro? (se descartarán la passphrase y el descriptor)", diff --git a/src/krux/translations/fr.py b/src/krux/translations/fr.py index c5f4b32f9..dbf5a6a93 100644 --- a/src/krux/translations/fr.py +++ b/src/krux/translations/fr.py @@ -67,6 +67,7 @@ "Vérifiez que l'adresse appartient à ce portefeuille\u2009?", "%d adresses vérifiées sans correspondance.", "Recherche de carte SD…", + "Cliquez pour lire", "Confirmer le code de non compromis", "Convertir le datum", "Impossible de déterminer l'adresse de monnaie.", @@ -135,6 +136,7 @@ "Plan du Flash", "Outils Flash", "Flash rempli par l'entropie de la caméra", + "Retournez la plaque", "Diamètre de flûte", "Libre\u2009:", "Du stockage", @@ -200,6 +202,7 @@ "Réseau", "Nouveau Mnémonique", "Nouveau micrologiciel détecté.", + "Suivant", "Non", "Pas de phrase secrète", "Des caractères non ASCII ont été détectés dans votre phrase secrète. Krux ne peut garantir que d'autres portefeuilles obtiendront la même clé.", @@ -227,6 +230,7 @@ "Taux de plongée", "Type de politique", "Entropie faible\u2009!", + "Positionnez la plaque", "Puissance", "Appuyez sur PAGE pour annuler.", "PAGE pour changer de mode", @@ -370,6 +374,9 @@ "Mot %d", "Numéros de mots", "Mots", + "Mots 1-12", + "Mots 13-24", + "Mots 7-12", "Résultat XOR", "XOR avec", "XOR mnémonique actuel avec un autre\u2009? (la phrase secrète et le descripteur seront supprimés)", diff --git a/src/krux/translations/ja.py b/src/krux/translations/ja.py index c002e687a..5d0832a39 100644 --- a/src/krux/translations/ja.py +++ b/src/krux/translations/ja.py @@ -67,6 +67,7 @@ "このアドレスがこのウォレットに属しているか確認しますか?", "%d のアドレスを確認しましたが、一致するものはありませんでした.", "SDカードを確認しています…", + "クリックして読み取る", "改ざんチェックコードの確認", "データムの変換", "変更先住所を特定できませんでした.", @@ -135,6 +136,7 @@ "フラッシュマップ", "Flashツール", "カメラエントロピーで満たされたフラッシュ", + "プレートを裏返す", "フルートディアメーター", "フリー:", "ストレージから", @@ -200,6 +202,7 @@ "ネットワーク", "新しい Mnemonic", "新しいファームウェアが検出されました.", + "次へ", "いいえ", "パスフレーズなし", "パスフレーズに非ASCII文字が検出されました.Kruxは、他のウォレットが同じキーを取得することを保証することはできません.", @@ -227,6 +230,7 @@ "沈下率", "証券の種類", "低品質なエントロピー!", + "プレートを配置", "出力", "PAGEを押してキャンセルします.", "PAGEを押してモードを切り替えます", @@ -370,6 +374,9 @@ "単語 %d", "単語番号", "単語", + "単語 1-12", + "単語 13-24", + "単語 7-12", "XOR結果", "XOR With", "XOR現在のニーモニックと別のニーモニック? (パスフレーズと記述子は破棄されます)", diff --git a/src/krux/translations/ko.py b/src/krux/translations/ko.py index 4bb8daebb..798edd10b 100644 --- a/src/krux/translations/ko.py +++ b/src/krux/translations/ko.py @@ -67,6 +67,7 @@ "해당 주소가 이 지갑에 속하는지 확인하시겠습니까?", "일치하는 주소가 없는 %d 개를 확인했습니다.", "SD 카드 확인 중…", + "읽으려면 클릭", "탬퍼 체크 코드 확인", "날짜 변환", "변경 주소를 확인할 수 없습니다.", @@ -135,6 +136,7 @@ "플래시 맵", "플래시 도구", "카메라 엔트로피로 가득 찬 플래시", + "플레이트 뒤집기", "플루트 직경", "여유 공간:", "저장공간에서 불러오기", @@ -200,6 +202,7 @@ "네트워크", "새로운 니모닉", "새로운 펌웨어가 감지되었습니다.", + "다음", "아니요", "패스프레이즈 없음", "암호에서 ASCII가 아닌 문자가 감지되었습니다. Krux는 다른 지갑이 동일한 키를 파생할 것이라고 보장할 수 없습니다.", @@ -227,6 +230,7 @@ "침수율", "정책 유형", "엔트로피가 부족합니다!", + "플레이트 배치", "출력", "PAGE로 취소.", "모드를 전환하려면 PAGE를 누르세요", @@ -370,6 +374,9 @@ "%d 단어", "단어 번호(1-2048)", "시드문구", + "단어 1-12", + "단어 13-24", + "단어 7-12", "XOR 결과", "XOR With", "다른 XOR 전류 니모닉? (패스프레이즈와 디스크립터가 폐기됩니다)", diff --git a/src/krux/translations/nl.py b/src/krux/translations/nl.py index 968f406f7..8d402964c 100644 --- a/src/krux/translations/nl.py +++ b/src/krux/translations/nl.py @@ -67,6 +67,7 @@ "Controleer of dit adres bij deze portemonnee hoort?", "%d adressen gecontroleerd zonder overeenkomsten.", "Controleren op SD-kaart…", + "Klik om te lezen", "Bevestig de sabotagecontrolecode", "Datum converteren", "Kan adreswijziging niet bepalen.", @@ -135,6 +136,7 @@ "Flash Map", "Flash Tools", "Flash gevuld met camera-entropie", + "Draai de plaat om", "Fluit diameter", "Vrij:", "Uit data-opslag", @@ -200,6 +202,7 @@ "Netwerk", "Geheugensteun aanmaken", "Nieuwe firmware gevonden.", + "Volgende", "Nee", "Geen wachtwoordzin", "Er zijn niet-ASCII-tekens gedetecteerd in uw wachtwoordzin. Krux kan niet garanderen dat andere portefeuilles dezelfde sleutel zullen afleiden.", @@ -227,6 +230,7 @@ "Duik tarief", "Type beleid", "Slechte entropie!", + "Positioneer de plaat", "Vermogen", "Druk op PAGE om te annuleren.", "PAGINA om de modus te wisselen", @@ -370,6 +374,9 @@ "Woord %d", "Woord nummers", "Woorden", + "Woorden 1-12", + "Woorden 13-24", + "Woorden 7-12", "XOR-resultaat", "XOR met", "XOR huidig geheugensteuntje met een ander? (passphrase en descriptor worden weggegooid)", diff --git a/src/krux/translations/pt.py b/src/krux/translations/pt.py index d26a53107..b0dafe661 100644 --- a/src/krux/translations/pt.py +++ b/src/krux/translations/pt.py @@ -67,6 +67,7 @@ "Checar se o endereço pertence a esta carteira?", "%d endereços checados sem correspondência.", "Procurando por cartão SD…", + "Clique para ler", "Confirmar código de verificação de integridade", "Converter dados", "Não foi possível determinar endereços de troco.", @@ -135,6 +136,7 @@ "Mapa da Flash", "Ferramentas da Flash", "Memória flash preenchida com entropia da câmera", + "Vire a placa", "Diâmetro da Fresa", "Livre:", "Do armazenamento", @@ -200,6 +202,7 @@ "Rede", "Novo Mnemônico", "Novo firmware detectado.", + "Próximo", "Não", "Sem Senha", "Caracteres não ASCII foram detectados em sua senha. A Krux não pode garantir que outras carteiras obtenham a mesma chave.", @@ -227,6 +230,7 @@ "Taxa de Mergulho", "Tipo de Política", "Entropia fraca!", + "Posicione a placa", "Potência", "Pressione PAGE para cancelar.", "Pressione PAGE para alternar o modo", @@ -370,6 +374,9 @@ "Palavra %d", "Números das Palavras", "Palavras", + "Palavras 1-12", + "Palavras 13-24", + "Palavras 7-12", "Resultado XOR", "XOR com", "XOR mnemônico atual com outro? (senha e descritor serão descartados)", diff --git a/src/krux/translations/ru.py b/src/krux/translations/ru.py index 1327eb778..15176beb6 100644 --- a/src/krux/translations/ru.py +++ b/src/krux/translations/ru.py @@ -67,6 +67,7 @@ "Проверить, что адрес принадлежит этому кошельку?", "Проверено %d адресов без совпадений.", "Проверка SD-карты…", + "Нажмите для чтения", "Подтвердите код проверки вскрытия", "Преобразовать датум", "Не удалось определить адрес изменения.", @@ -135,6 +136,7 @@ "Карта флэша", "Flash Tools", "Флэш заполнен энтропией камеры", + "Переверните пластину", "Диаметр Флюта", "Свободно:", "Из Памяти", @@ -200,6 +202,7 @@ "Сеть", "Новая Мнемоника", "Обнаружена новая прошивка.", + "Далее", "Нет", "Нет Фраза-пароль", "В вашей парольной фразе обнаружены символы, отличные от ASCII. Krux не может гарантировать, что другие кошельки получат тот же ключ.", @@ -227,6 +230,7 @@ "Скорость Погружения", "Тип политики", "Плохая энтропия!", + "Расположите пластину", "Мощность", "Нажмите PAGE, чтобы отменить.", "PAGE, чтобы переключить режим", @@ -370,6 +374,9 @@ "Слово %d", "Числа Слов", "Слова", + "Слова 1-12", + "Слова 13-24", + "Слова 7-12", "Результат XOR", "XOR с", "XOR current mnemonic with another one? (парольная фраза и дескриптор будут отброшены)", diff --git a/src/krux/translations/tr.py b/src/krux/translations/tr.py index fd1cc298d..518293201 100644 --- a/src/krux/translations/tr.py +++ b/src/krux/translations/tr.py @@ -67,6 +67,7 @@ "Bu adresin, bu cüzdana ait olduğunu kontrol et?", "Eşleşmeyen %d adres kontrol edildi.", "SD kart kontrol ediliyor…", + "Okumak için tıklayın", "Kurcalama Kontrol Kodunu Onayla", "Veriyi Dönüştür", "Değişiklik adresi belirlenemedi.", @@ -135,6 +136,7 @@ "Flash Haritası", "Flash Araçları", "Flash kamera entropisi ile dolduruldu", + "Plakayı çevirin", "Flute Çapı", "Boş:", "Depolamadan Seç", @@ -200,6 +202,7 @@ "Ağ", "Yeni Mnemonic", "Yeni donanım yazılımı tespit edildi.", + "İleri", "Hayır", "Parola Yok", "Parolanızda ASCII olmayan karakterler algılandı. Krux, diğer cüzdanların aynı anahtarı türeteceğini garanti edemez.", @@ -227,6 +230,7 @@ "Dalış Hızı", "Politika türü", "Düşük entropi!", + "Plakayı konumlandırın", "Güç", "İptal etmek için PAGE tuşuna basın.", "PAGE modunu değiştir", @@ -370,6 +374,9 @@ "Kelime %d", "Kelime Numaraları", "Kelimeler", + "Kelimeler 1-12", + "Kelimeler 13-24", + "Kelimeler 7-12", "XOR Sonucu", "XOR ile", "XOR başka biriyle geçerli anımsatıcı? (parola ve tanımlayıcı atılacak)", diff --git a/src/krux/translations/vi.py b/src/krux/translations/vi.py index 28e26bfa8..527e74ab9 100644 --- a/src/krux/translations/vi.py +++ b/src/krux/translations/vi.py @@ -67,6 +67,7 @@ "Kiểm tra địa chỉ đó có thuộc về ví này không?", "Đã kiểm tra %d địa chỉ không khớp.", "Đang kiểm tra thẻ SD…", + "Nhấn để đọc", "Xác nhận mã kiểm tra giả mạo", "Chuyển đổi dữ liệu", "Không thể xác định địa chỉ thay đổi.", @@ -135,6 +136,7 @@ "Bản đồ Flash", "Công cụ Flash", "Đèn flash chứa đầy entropy của máy ảnh", + "Lật tấm", "Đường kính mũi cắt CNC", "Khả dụng:", "Từ bộ lưu trữ", @@ -200,6 +202,7 @@ "Mạng lưới", "Mnemonic mới", "Phát hiện phần sụn mới.", + "Tiếp", "Không", "Không có cụm mật khẩu", "Các ký tự không phải ASCII đã được phát hiện trong cụm mật khẩu của bạn. Krux không thể đảm bảo rằng các ví khác sẽ lấy được cùng một khóa.", @@ -227,6 +230,7 @@ "Tỷ lệ sụt giảm", "Loại Chính sách", "Entropy kém!", + "Đặt tấm", "Công suất", "Nhấn PAGE để hủy.", "PAGE để chuyển chế độ", @@ -370,6 +374,9 @@ "Kí tự %d", "Từ số", "Từ ngữ", + "Từ 1-12", + "Từ 13-24", + "Từ 7-12", "Kết quả XOR", "XOR với", "Ghi nhớ hiện tại của XOR với một cụm từ khác? (cụm mật khẩu và mô tả sẽ bị loại bỏ)", diff --git a/src/krux/translations/zh.py b/src/krux/translations/zh.py index 24f26a3a0..45894c738 100644 --- a/src/krux/translations/zh.py +++ b/src/krux/translations/zh.py @@ -67,6 +67,7 @@ "检查该地址是否属于此钱包?", "已检查 %d 个不匹配的地址.", "检查卡…", + "点击读取", "确认防篡改检查码", "转换基准", "无法确定更改地址.", @@ -135,6 +136,7 @@ "Flash地图", "Flash工具", "Flash已用摄像头熵填充", + "翻转板", "刀具直径", "空闲:", "从存储中", @@ -200,6 +202,7 @@ "网络", "新助记词", "检测到新固件.", + "下一步", "否", "无 Passphrase ", "在您的密码短语中检测到非ASCII字符.Krux无法保证其他钱包将获得相同的密钥.", @@ -227,6 +230,7 @@ "下刀速度", "政策类型", "熵值低!", + "放置板", "功率", "按PAGE取消.", "按 PAGE 切换模式", @@ -370,6 +374,9 @@ "词 %d", "单词序号", "单词", + "单词 1-12", + "单词 13-24", + "单词 7-12", "XOR结果", "XOR With", "XOR当前助记符与其他助记符? (密码短语和描述符将被丢弃)", diff --git a/tests/pages/home_pages/test_mnemonic_xor.py b/tests/pages/home_pages/test_mnemonic_xor.py index 9d18cb446..106a6dd84 100644 --- a/tests/pages/home_pages/test_mnemonic_xor.py +++ b/tests/pages/home_pages/test_mnemonic_xor.py @@ -583,7 +583,7 @@ def test_export_xor_to_same_mnemonic_from_qrcode(mocker, amigo, tdata): BUTTON_ENTER, # Press "Via camera" BUTTON_ENTER, # QRCode BUTTON_ENTER, # Press "Yes" to accept part words (will raise error) - *([BUTTON_PAGE] * 5), # Move to back + *([BUTTON_PAGE] * 6), # Move to back BUTTON_ENTER, # Press back *([BUTTON_PAGE] * 3), # Move to back BUTTON_ENTER, # Press back @@ -625,7 +625,7 @@ def test_export_xor_to_inverted_mnemonic_from_qrcode(mocker, amigo, tdata): BUTTON_ENTER, # Press "Via camera" BUTTON_ENTER, # QRCodeCapture BUTTON_ENTER, # Press "Yes" to accept part words (will raise error) - *([BUTTON_PAGE] * 5), # Move to back + *([BUTTON_PAGE] * 6), # Move to back BUTTON_ENTER, # Press back *([BUTTON_PAGE] * 3), # Move to back BUTTON_ENTER, # Press back @@ -669,7 +669,7 @@ def test_export_xor_low_entropy_mnemonic_from_qrcode(mocker, amigo, tdata): BUTTON_ENTER, # Press "Via camera" BUTTON_ENTER, # QRCodeCapture BUTTON_ENTER, # Press "Yes" to accept part words (will raise error) - *([BUTTON_PAGE] * 5), # Move to back + *([BUTTON_PAGE] * 6), # Move to back BUTTON_ENTER, # Press back *([BUTTON_PAGE] * 3), # Move to back BUTTON_ENTER, # Press back