Full l10n integration; Initial possible v0.8.5

This commit is contained in:
kdmukai
2024-11-01 17:03:18 -05:00
parent 2caa7ad1de
commit f64f3945a8
25 changed files with 1019 additions and 495 deletions
-75
View File
@@ -1,75 +0,0 @@
# Babel Multilanguage Support
### "Wrapping" text for translation
All you have to do in your code is wrap each piece of English text with the `gettext` shorthand `_()`:
* Wrap python strings: `error="No device was selected."` becomes `error=_("No device was selected.")`
* Use `.format()` to wrap strings with variable injections:
```
mystr = f"My dad's name is {dad.name} and my name is {self.name}."
mystr = _("My dad's name is {} and my name is {}").format(dad.name, self.name)
```
If there are a lot of variables to inject, placeholder names can be used:
```
mystr = _("My dad's name is {dad_name} and my name is {my_name}").format(dad_name=dad.name, my_name=self.name)
```
## Set up `babel`
Install the additional dependency in your local dev environment
```bash
pip install -r babel/requirements-babel.txt
```
Make sure that your local repo has fetched the `seedsigner-translations` submodule. It's configured to add it in src/seedsigner/resources.
```bash
# Need --remote in order to respect the target branch listed in .gitmodules
git submodule update --remote
```
### Pre-configured `babel` commands
The `setup.cfg` file in the project root specifies params for the various `babel` commands discussed below.
### Rescanning for text that needs translations
Re-generate the `messages.pot` file:
```bash
python setup.py extract_messaages
```
This will rescan all wrapped text, picking up new strings as well as updating existings strings that have been edited.
_TODO: Github Action to auto-generate messages.pot and fail a PR update if the PR has an out of date messages.pot._
### Making new text available to translators
Upload the master `messages.pot` to Transifex. It will automatically update each language with the new or changed source strings.
_TODO: Look into Transifex options to automatically pull updates._
### Once new translations are complete
The translation file for each language will need to be downloaded via Transifex's "Download for use" option (sends you a `messages.po` file for that language).
This updated `messages.po` will need to be added to the seedsigner-translations repo in l10n/`{TARGET_LOCALE}`/LC_MESSAGES.
### Compile all the translations
The `messages.po` files must be compiled into `*.mo` files:
```bash
python compile_messages
# Or target a specific language code:
python compile_messages -l es
```
## Keep the seedsigner-translations repo up to date
The *.po files for each language and their compiled *.mo files should all be kept up to date in the seedsigner-translations repo.
_TODO: Github Actions automation to regenerate / verify that the *.mo files have been updated after *.po changes._
-72
View File
@@ -1,72 +0,0 @@
# i18n notes
#
# Format is "Pattern filename space-separated-list-of-line-numbers-with-pattern-prefixes" as of 20 November 2022
#
# kdmukai/intial_multilanguage branch already has plenty of work
# * copied kdmukai's branch
# * merged dev in into his branch.
# * following his lead to change text literals and f-string substitutions.
# * NOTE: TODO: add TranslatorNotes once I understand when they are needed!!!
#
# Common coding patterns noted: (source code lines below are prefixed w/[LFDECP) to identify pattern)
#
# * L) Literal text strings.
#
# * F) F-strings with placeholders.
#
# * V) variables with values set to a previously translated constant, ie _(display_name)
#
# * D) Default variables in baseclasses like "Button Label": is it necessary? user should never see these, else complain?
#
# * E) Exception messages: is it really necessary? only if frequent, else let user complain?
#
# * P) Proper names, partner logos, Coordinators... is it really of any concern at all?
#
# * print() debug messages to the C)onsole or /dev/null do not need translations? Not even included here.
#
#
# While reading the notes below:
# Each file and line-number are preceded by the type of i18n change needed, if truly 'needed'
# A preceding # indicates there is nothing left to do, it's already i18n-ized.
# A preceding ? indicates that I should give this another look, or a deeper look.
# A preceding [DEP] might indicate nothing to do... if we really don't want to i18n these patterns.
#
E seedsigner/controller.py E117 E161 E168
ED seedsigner/gui/components.py E285 E324 E457 E498 E591 D1096 D1311
E seedsigner/gui/keyboard.py E204 E207 E485 E587
#L seedsigner/gui/renderer.py #L144
DE#LF seedsigner/gui/screens/screen.py D193 E524 #L756 #L771 #L885 #L888 #L900 #F1112
#LF seedsigner/gui/screens/psbt_screens.py #L526 #L543? #L553 #L30 #L32 #L89 #L91 #L92 #L94 #F96 #F97 #F100 #L128 #L132 #L141 #L144 #L146 #F148 #L150 #L155 #L466 #L467 #L473 #L485 #L560 #F571 #FL667 #L677 #L689 #L703
#L seedsigner/gui/screens/scan_screens.py #L22 #L138 #L139 #L140 #L156
?#L seedsigner/gui/screens/settings_screens.py (_().replace() hmmm ?#L294) #L26 #L59 #L130 #L141 #L162 #L176 #L222 #L289
#LF seedsigner/gui/screens/tools_screens.py #L41 #L72 #L99 #L119 #F150 #L161 #F167 #F177 #F189 #F193 #F200 #F241 #L274 #F311 #L352 #L354 #L369 #L385 #L393 #F402 #F410 #F418
#L seedsigner/gui/screens/main_menu_screens.py #L12 #L24 #L29 #L39 #L44
?#L seedsigner/gui/screens/seed_screens.py (why colon ?#L1033) #L411 #L424 #L555 #L561 #L571 #L587 #L593 #L597 #L606 #L616 #L625 #L638 #L1026 #L1121 #L1123 #L1131 #L1133 #L1147 #L1149 #L1226 #L1362 #L1419 #L1483 #L1500 #L1505 #L1518 #L1524 #L1532
E seedsigner/hardware/camera.py E36 E65
E seedsigner/hardware/ST7789.py E154
E seedsigner/helpers/embit_utils.py E25 E35 E43 E45 E47 E91 E95
E seedsigner/helpers/mnemonic_generation.py E22
? seedsigner/helpers/ur2/DID-NOT-LOOK
E#LF seedsigner/views/psbt_views.py E30 #L36 #L37 #L38 #L55 #L94 #L166 #L167 #L168 #L185 #L186 #L187 #L253 #259 #L262 #L331 #L335 #L336 #L338 #L339 #L358 #L360 #L441 #F443 #L445 #FL447 #L451 #L453 #L478 #L530 #L532 #L533 #L534
#L seedsigner/views/screensaver.py #L74
E#LF seedsigner/views/seed_views.py (Why this one ?#E1464) #L1637 #L54 #L57 #L78 #L79 #L80 #L81 #L90 #F127 #L187 #L188 #L192 #L194 #L229 #L230 #L260 #L300 #L301 #L342 #L343 #L348 #F350 #L384 #L385 #L386 #L387 #L388 #L389 #L390 #L420 #L476 #L477 #L510 #L511 #L515 #L554 #L556 #L641 #L683 #L684 #L693 #L830 #L855 #L856 #F836 #L902 #L903 #F952 #L992 #L993 #L997 #L999 #F1000 #L1018 #L1020 #L1021 #L1022 #L1044 #L1045 #L1049 #L1050 #L1071 #L1120 #L1121 #L1213 #L1214 #L1218 #L1248 #L1257 #L1258 #L1259 #L1221 #L1268 #L1269 #L1270 #L1272 #L1280 #L1281 #L1282 #L1284 #L1353 #L1354 #L1391 #L1392 #L1393 #L1396 #L1406 #L1413 #L1510 #L1511 #F1639 #L1642 #L1652 #L1653 #L1682 #L1683 #L1684 #L1685
#V seedsigner/views/settings_views.py #V26 #L20 #L21 #L36 #L39 #L46 #L54
E#LF seedsigner/views/tools_views.py E605 E616 #L29 #L30 #L31 #L32 #L35 #L112 #L113 #L117 #L184 #L185 #L189 #L238 #L239 #L243 #L275 #L276 #L277 #L376 #L379 #L401 #L402 #L430 #L431 #L432 #L433 #L451 #L538 #L539 #L589 #F628 #FL631
E#L seedsigner/views/view.py E66 #L124 #L125 #L126 #L127 #L142 #L145
E seedsigner/models/encode_qr.py E46 E104 E143 E146 E150 E153 E273 E356
E seedsigner/models/psbt_parser.py E96 E226 E229 E232 E241 E245 E255 E269
E seedsigner/models/seed.py E26 E42
E seedsigner/models/seed_storage.py E68
P#L seedsigner/models/settings_definition.py P37 P38 P39 P40 #V288 #L14 #L15 #L18 #L19 #L20 #L23 #L26 #L29 #L74 #L55 #L76 #L77 #L96 #L97 #L98 #L106 #L107 #L108 #L124 #L125 #L134 #L135 #L136 #L137 #L357 #L365 #L373 #L374 #L379 #L386 #L395 #L403 #L411 #L417 #L425 #L433 #L439 #L447 #L455 #L461 #L467 #L473
E seedsigner/models/settings.py E112 E134 E150 E153 E162 E165
E seedsigner/models/singleton.py E6 E24 E31
E seedsigner/models/threads.py E23
+143
View File
@@ -0,0 +1,143 @@
# Localization (l10n) Tech Notes
## "Wrapping" text for translation
All you have to do in your code is wrap each piece of English text with the `gettext` shorthand `_()`:
* Wrap python strings: `error="No device was selected."` becomes `error=_("No device was selected.")`
* Use `.format()` to wrap strings with variable injections:
```python
mystr = f"My dad's name is {dad.name} and my name is {self.name}."
mystr = _("My dad's name is {} and my name is {}").format(dad.name, self.name)
```
If there are a lot of variables to inject, placeholder names can be used:
```python
mystr = _("My dad's name is {dad_name} and my name is {my_name}").format(dad_name=dad.name, my_name=self.name)
```
* Use `ngettext` to dynamically handle singular vs plural forms based on an integer quantity:
```python
n = 1
print(ngettext("apple", "apples", n))
>> apple
n = 5
print(ngettext("apple", "apples", n))
>> apples
```
Transifex will ask translators to provide the singular and plural forms.
Note: Some languages like Arabic have multiple plural forms (e.g. 2-3 is one form, > 3 is another).
## Understanding `gettext` wrapping
Note that this wrapping serves TWO purposes:
1. Pre-translation: This is how we identify text that translators need to translate. Any strings wrapped with `_()` (or related calls like `ngettext`) will appear in translators' Transifex UI.
2. Post-translation: It returns the locale-specific translation for that source string (defaults to the English string itself if no translation is available).
In general, most SeedSigner code uses the `_()` wrapping only to identify the strings that need translation.
The second step -- fetching the translated string -- mostly happens in the base `Screen` classes. They ask for the translation just before the text is drawn on screen.
Basic rules:
* Wrap raw English strings so they'll be picked up for translation.
* In general, don't go out of your way to pass already translated text to `Screen` classes.
* The `Screen` itself should do most of the `_()` wrapping to translate its input attributes for final display.
* Basic gui `Component` classes (e.g. `Button`, `TextArea`) should ideally NOT wrap any text.
* Ensure that they are supplied with wrapped / already translated text.
Basic example:
```python
@dataclass
class SomeClass:
title: str = _("My Title")
```
In this case `title` is wrapped in order to get "My Title" into the translators' list. Note that because `title` is a class-level attribute, its string value will be determined at import time and
will NOT be re-evaluated when the class is eventually used. That means that the locale-specific translation will NOT be returned later:
```python
from blah import SomeClass
print(SomeClass.title)
>> My Title
# set locale to some other language and then...
os.environ['LANGUAGE'] = 'es'
my_instance = SomeClass()
print(my_instance.title)
>> My Title
```
To get the translated value, we'd have to pass the attribute itself into `_()`. This feels redundant since the original string was already wrapped in the class definition, but we have to
work around the realities of python imports, etc. This time the wrapping is saying, "Hey, take the value you're holding in that attribute/variable and see if it has an entry in our locale-specific
translation library."
```python
os.environ['LANGUAGE'] = 'es'
print(_(SomeClass.title))
>> Mi Titulo
my_instance = SomeClass()
print(_(my_instance.title))
>> Mi Titulo
```
## Set up localization dependencies
```bash
pip install -r l10n/requirements-l10n.txt
```
Make sure that your local repo has fetched the `seedsigner-translations` submodule. It's configured to add it in src/seedsigner/resources.
```bash
# Need --remote in order to respect the target branch listed in .gitmodules
git submodule update --remote
```
### Pre-configured `babel` commands
The `setup.cfg` file in the project root specifies params for the various `babel` commands discussed below.
### Rescanning for text that needs translations
Re-generate the `messages.pot` file:
```bash
python setup.py extract_messaages
```
This will rescan all wrapped text, picking up new strings as well as updating existings strings that have been edited.
_TODO: Github Action to auto-generate messages.pot and fail a PR update if the PR has an out of date messages.pot._
### Making new text available to translators
Upload the master `messages.pot` to Transifex. It will automatically update each language with the new or changed source strings.
_TODO: Look into Transifex options to automatically pull updates._
### Once new translations are complete
The translation file for each language will need to be downloaded via Transifex's "Download for use" option (sends you a `messages.po` file for that language).
This updated `messages.po` will need to be added to the seedsigner-translations repo in l10n/`{TARGET_LOCALE}`/LC_MESSAGES.
### Compile all the translations
The `messages.po` files must be compiled into `*.mo` files:
```bash
python setup.py compile_catalog
# Or target a specific language code:
python setup.py compile_catalog -l es
```
### Unused babel commands
Transifex eliminates the need for the `init_catalog` and `update_catalog` commands.
## Keep the seedsigner-translations repo up to date
The *.po files for each language and their compiled *.mo files should all be kept up to date in the seedsigner-translations repo.
_TODO: Github Actions automation to regenerate / verify that the *.mo files have been updated after *.po changes._
+120 -25
View File
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: seedsigner 0.7.0\n"
"Project-Id-Version: seedsigner 0.8.5\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-10-11 14:02-0500\n"
"POT-Creation-Date: 2024-11-01 16:48-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -32,6 +32,7 @@ msgid "btc"
msgstr ""
#: src/seedsigner/gui/components.py src/seedsigner/gui/screens/psbt_screens.py
#: src/seedsigner/models/settings_definition.py
msgid "sats"
msgstr ""
@@ -113,6 +114,11 @@ msgstr ""
msgid "fee"
msgstr ""
#. Technical term, should probably NOT be translated in most languages
#: src/seedsigner/gui/screens/psbt_screens.py
msgid "OP_RETURN"
msgstr ""
#. Label for a change output in the PSBT Overview flow diagram
#. Used in a sentence describing the address type (change or receive)
#: src/seedsigner/gui/screens/psbt_screens.py
@@ -166,6 +172,11 @@ msgstr ""
msgid "Address verified!"
msgstr ""
#. Shown when displaying OP_RETURN as non-human-readable hexadecimal data
#: src/seedsigner/gui/screens/psbt_screens.py
msgid "raw hex data"
msgstr ""
#: src/seedsigner/gui/screens/psbt_screens.py
msgid "Sign PSBT"
msgstr ""
@@ -175,11 +186,13 @@ msgid "Click to approve this transaction"
msgstr ""
#: src/seedsigner/gui/screens/scan_screens.py
msgid "< back | "
#: src/seedsigner/gui/screens/tools_screens.py
msgid "back"
msgstr ""
#. Inserts the percentage value of the animated QR scan progress
#: src/seedsigner/gui/screens/scan_screens.py
msgid "{}% Complete"
msgid "{}%"
msgstr ""
#. Increase QR code screen brightness
@@ -235,6 +248,10 @@ msgstr ""
msgid "Please wait about 30 seconds before disconnecting power."
msgstr ""
#: src/seedsigner/gui/screens/screen.py
msgid "Just Unplug It"
msgstr ""
#: src/seedsigner/gui/screens/screen.py
msgid "It is safe to disconnect power at any time."
msgstr ""
@@ -310,6 +327,7 @@ msgid "Xpub"
msgstr ""
#: src/seedsigner/gui/screens/seed_screens.py
#: src/seedsigner/models/settings_definition.py
#: src/seedsigner/views/seed_views.py
msgid "BIP-39 Passphrase"
msgstr ""
@@ -467,15 +485,27 @@ msgstr ""
msgid "Settings QR"
msgstr ""
#: src/seedsigner/gui/screens/settings_screens.py
msgid "Settings updated..."
msgstr ""
#: src/seedsigner/gui/screens/settings_screens.py
#: src/seedsigner/views/settings_views.py src/seedsigner/views/view.py
msgid "Home"
msgstr ""
#. Instructions when reviewing an image; "reshoot" must point left, "accept"
#. must point right
#: src/seedsigner/gui/screens/tools_screens.py
msgid " < reshoot | accept > "
msgid "click joystick"
msgstr ""
#. A prompt to the user to either accept or reshoot the image
#: src/seedsigner/gui/screens/tools_screens.py
msgid "reshoot"
msgstr ""
#. A prompt to the user to either accept or reshoot the image
#: src/seedsigner/gui/screens/tools_screens.py
msgid "accept"
msgstr ""
#. current roll number vs total rolls (e.g. roll 7 of 50)
@@ -510,7 +540,7 @@ msgstr ""
msgid "Tails = 0"
msgstr ""
#. A labeled presentation of a user-supplied value
#. The additional entropy the user supplied (e.g. coin flips)
#: src/seedsigner/gui/screens/tools_screens.py
msgid "Your input: \"{}\""
msgstr ""
@@ -562,6 +592,18 @@ msgstr ""
msgid "Required"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "BTC"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Threshold at 0.01"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "BTC | sats hybrid"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "0°"
msgstr ""
@@ -578,14 +620,17 @@ msgstr ""
msgid "270°"
msgstr ""
#. QR code density option: Low, Medium, High
#: src/seedsigner/models/settings_definition.py
msgid "Low"
msgstr ""
#. QR code density option: Low, Medium, High
#: src/seedsigner/models/settings_definition.py
msgid "Medium"
msgstr ""
#. QR code density option: Low, Medium, High
#: src/seedsigner/models/settings_definition.py
msgid "High"
msgstr ""
@@ -620,7 +665,11 @@ msgid "Native Segwit"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Nested Segwit (legacy)"
msgid "Nested Segwit"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Legacy"
msgstr ""
#: src/seedsigner/models/settings_definition.py
@@ -631,6 +680,11 @@ msgstr ""
msgid "Custom Derivation"
msgstr ""
#. Terminology used by Electrum seeds; equivalent to bip39 passphrase
#: src/seedsigner/models/settings_definition.py
msgid "Custom Extension"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Language"
msgstr ""
@@ -691,6 +745,10 @@ msgstr ""
msgid "BIP-85 child seeds"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Native Segwit only"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Message signing"
msgstr ""
@@ -730,6 +788,17 @@ msgstr ""
msgid "Enter 24-word seed"
msgstr ""
#: src/seedsigner/views/psbt_views.py src/seedsigner/views/seed_views.py
#: src/seedsigner/views/tools_views.py
msgid "Enter Electrum seed"
msgstr ""
#. Inserts fingerprint w/"?" to indicate that this seed can't sign the current
#. PSBT
#: src/seedsigner/views/psbt_views.py
msgid "{} (?)"
msgstr ""
#: src/seedsigner/views/psbt_views.py
msgid "Select Signer"
msgstr ""
@@ -775,15 +844,19 @@ msgstr ""
msgid "Next Recipient"
msgstr ""
#. The amount you're receiving back from the transaction
#: src/seedsigner/views/psbt_views.py
msgid "Your Change"
msgid "Skip Verification"
msgstr ""
#: src/seedsigner/views/psbt_views.py
msgid "Verify Multisig Change"
msgstr ""
#. The amount you're receiving back from the transaction
#: src/seedsigner/views/psbt_views.py
msgid "Your Change"
msgstr ""
#: src/seedsigner/views/psbt_views.py
msgid "Self-Transfer"
msgstr ""
@@ -971,6 +1044,18 @@ msgstr ""
msgid "Edit passphrase"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Discard passphrase"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Discard passphrase?"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Your current passphrase entry will be erased"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Keep Seed"
msgstr ""
@@ -984,6 +1069,14 @@ msgstr ""
msgid "Discard Seed?"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Electrum warning"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Some features are disabled for Electrum seeds."
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Verify Addr"
msgstr ""
@@ -1157,10 +1250,6 @@ msgstr ""
msgid "Sig type can't be auto-detected from this address. Please specify:"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Can't validate a single sig addr without specifying a seed"
msgstr ""
#. Option when scanning for a matching address; skips ten addresses ahead
#: src/seedsigner/views/seed_views.py
msgid "Skip 10"
@@ -1170,6 +1259,10 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Can't validate a single sig addr without specifying a seed"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "multisig"
msgstr ""
@@ -1202,15 +1295,15 @@ msgstr ""
msgid "Return to PSBT"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Signing messages for custom derivation paths not supported"
msgstr ""
#. Inserts mainnet/testnet/regtest and derivation path
#: src/seedsigner/views/seed_views.py
msgid "Current network setting ({}) doesn't match {}"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Signing messages for custom derivation paths not supported"
msgstr ""
#: src/seedsigner/views/settings_views.py
msgid "I/O test"
msgstr ""
@@ -1255,12 +1348,14 @@ msgstr ""
msgid "Mnemonic Length?"
msgstr ""
#. Inserts the number of dice rolls needed for a 12-word mnemonic
#: src/seedsigner/views/tools_views.py
msgid "12 words (50 rolls)"
msgid "12 words ({} rolls)"
msgstr ""
#. Inserts the number of dice rolls needed for a 24-word mnemonic
#: src/seedsigner/views/tools_views.py
msgid "24 words (99 rolls)"
msgid "24 words ({} rolls)"
msgstr ""
#: src/seedsigner/views/tools_views.py
@@ -1351,6 +1446,10 @@ msgstr ""
msgid "Reset / Power"
msgstr ""
#: src/seedsigner/views/view.py
msgid "This is still on our to-do list!"
msgstr ""
#: src/seedsigner/views/view.py
msgid "Work In Progress"
msgstr ""
@@ -1359,10 +1458,6 @@ msgstr ""
msgid "Not Yet Implemented"
msgstr ""
#: src/seedsigner/views/view.py
msgid "This is still on our to-do list!"
msgstr ""
#: src/seedsigner/views/view.py
msgid "Back to Main Menu"
msgstr ""
+1 -1
View File
@@ -13,7 +13,7 @@ description = "Build an offline, airgapped Bitcoin signing device for less than
name = "seedsigner"
readme = "README.md"
requires-python = ">=3.10"
version = "0.8.0"
version = "0.8.5"
[project.urls]
"Bug Tracker" = "https://github.com/SeedSigner/seedsigner/issues"
+515
View File
@@ -0,0 +1,515 @@
{
"settings_entries": [
{
"category": "system",
"attr_name": "locale",
"abbreviated_name": "lang",
"display_name": "Language",
"visibility": "general",
"type": "select_1",
"help_text": null,
"selection_options": [
{
"display_name": "\u010de\u0161tina",
"value": "cs"
},
{
"display_name": "English",
"value": "en"
},
{
"display_name": "Fran\u00e7ais",
"value": "fr"
},
{
"display_name": "Deutsch",
"value": "de"
},
{
"display_name": "Portugu\u00eas",
"value": "pt"
},
{
"display_name": "\u0440\u0443\u0441\u0441\u043a\u0438\u0439",
"value": "ru"
},
{
"display_name": "Espa\u00f1ol",
"value": "es"
}
],
"default_value": "en"
},
{
"category": "system",
"attr_name": "wordlist_language",
"abbreviated_name": "wordlist_lang",
"display_name": "Mnemonic language",
"visibility": "hidden",
"type": "select_1",
"help_text": null,
"selection_options": [
{
"display_name": "English",
"value": "en"
}
],
"default_value": "en"
},
{
"category": "system",
"attr_name": "persistent_settings",
"abbreviated_name": "persistent",
"display_name": "Persistent settings",
"visibility": "general",
"type": "enabled_disabled",
"help_text": "Store Settings on SD card",
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "D"
},
{
"category": "wallet",
"attr_name": "coordinators",
"abbreviated_name": "coords",
"display_name": "Coordinator software",
"visibility": "general",
"type": "multiselect",
"help_text": null,
"selection_options": [
{
"display_name": "BlueWallet",
"value": "bw"
},
{
"display_name": "Nunchuk",
"value": "nun"
},
{
"display_name": "Sparrow",
"value": "spa"
},
{
"display_name": "Specter Desktop",
"value": "spd"
},
{
"display_name": "Keeper",
"value": "kpr"
}
],
"default_value": [
"bw",
"nun",
"spa",
"spd"
]
},
{
"category": "system",
"attr_name": "denomination",
"abbreviated_name": "denom",
"display_name": "Denomination display",
"visibility": "general",
"type": "select_1",
"help_text": null,
"selection_options": [
{
"display_name": "BTC",
"value": "btc"
},
{
"display_name": "sats",
"value": "sats"
},
{
"display_name": "Threshold at 0.01",
"value": "thr"
},
{
"display_name": "BTC | sats hybrid",
"value": "hyb"
}
],
"default_value": "thr"
},
{
"category": "features",
"attr_name": "network",
"abbreviated_name": null,
"display_name": "Bitcoin network",
"visibility": "advanced",
"type": "select_1",
"help_text": null,
"selection_options": [
{
"display_name": "Mainnet",
"value": "M"
},
{
"display_name": "Testnet",
"value": "T"
},
{
"display_name": "Regtest",
"value": "R"
}
],
"default_value": "M"
},
{
"category": "features",
"attr_name": "qr_density",
"abbreviated_name": null,
"display_name": "QR code density",
"visibility": "advanced",
"type": "select_1",
"help_text": null,
"selection_options": [
{
"display_name": "Low",
"value": "L"
},
{
"display_name": "Medium",
"value": "M"
},
{
"display_name": "High",
"value": "H"
}
],
"default_value": "M"
},
{
"category": "features",
"attr_name": "xpub_export",
"abbreviated_name": null,
"display_name": "Xpub export",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "E"
},
{
"category": "features",
"attr_name": "sig_types",
"abbreviated_name": "sigs",
"display_name": "Sig types",
"visibility": "advanced",
"type": "multiselect",
"help_text": null,
"selection_options": [
{
"display_name": "Single Sig",
"value": "ss"
},
{
"display_name": "Multisig",
"value": "ms"
}
],
"default_value": [
"ss",
"ms"
]
},
{
"category": "features",
"attr_name": "script_types",
"abbreviated_name": "scripts",
"display_name": "Script types",
"visibility": "advanced",
"type": "multiselect",
"help_text": null,
"selection_options": [
{
"display_name": "Native Segwit",
"value": "nat"
},
{
"display_name": "Nested Segwit",
"value": "nes"
},
{
"display_name": "Legacy",
"value": "leg"
},
{
"display_name": "Taproot",
"value": "tr"
},
{
"display_name": "Custom Derivation",
"value": "cus"
}
],
"default_value": [
"nat",
"nes",
"tr"
]
},
{
"category": "features",
"attr_name": "xpub_details",
"abbreviated_name": null,
"display_name": "Show xpub details",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "E"
},
{
"category": "features",
"attr_name": "passphrase",
"abbreviated_name": null,
"display_name": "BIP-39 passphrase",
"visibility": "advanced",
"type": "select_1",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
},
{
"display_name": "Required",
"value": "R"
}
],
"default_value": "E"
},
{
"category": "features",
"attr_name": "camera_rotation",
"abbreviated_name": "camera",
"display_name": "Camera rotation",
"visibility": "advanced",
"type": "select_1",
"help_text": null,
"selection_options": [
{
"display_name": "0\u00b0",
"value": 0
},
{
"display_name": "90\u00b0",
"value": 90
},
{
"display_name": "180\u00b0",
"value": 180
},
{
"display_name": "270\u00b0",
"value": 270
}
],
"default_value": 180
},
{
"category": "features",
"attr_name": "compact_seedqr",
"abbreviated_name": null,
"display_name": "Compact SeedQR",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "E"
},
{
"category": "features",
"attr_name": "bip85_child_seeds",
"abbreviated_name": "bip85",
"display_name": "BIP-85 child seeds",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "D"
},
{
"category": "features",
"attr_name": "electrum_seeds",
"abbreviated_name": "electrum",
"display_name": "Electrum seeds",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": "Native Segwit only",
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "D"
},
{
"category": "features",
"attr_name": "message_signing",
"abbreviated_name": null,
"display_name": "Message signing",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "D"
},
{
"category": "features",
"attr_name": "privacy_warnings",
"abbreviated_name": "priv_warn",
"display_name": "Show privacy warnings",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "E"
},
{
"category": "features",
"attr_name": "dire_warnings",
"abbreviated_name": "dire_warn",
"display_name": "Show dire warnings",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "E"
},
{
"category": "features",
"attr_name": "qr_brightness_tips",
"abbreviated_name": null,
"display_name": "Show QR brightness tips",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "E"
},
{
"category": "features",
"attr_name": "partner_logos",
"abbreviated_name": "partners",
"display_name": "Show partner logos",
"visibility": "advanced",
"type": "enabled_disabled",
"help_text": null,
"selection_options": [
{
"display_name": "Enabled",
"value": "E"
},
{
"display_name": "Disabled",
"value": "D"
}
],
"default_value": "E"
},
{
"category": "system",
"attr_name": "qr_background_color",
"abbreviated_name": "qr_brightness",
"display_name": "QR background color",
"visibility": "hidden",
"type": "free_entry",
"help_text": null,
"selection_options": null,
"default_value": 62
}
]
}
+1 -7
View File
@@ -3,14 +3,8 @@ add_comments = TRANSLATOR_NOTE:
strip_comments = yes
add_location = file
input_dirs = src
output_file = babel/messages.pot
output_file = l10n/messages.pot
[update_catalog]
ignore_pot_creation_date = yes
no_fuzzy_matching = yes
no_wrap = yes
input_file = babel/messages.pot
output_dir = src/seedsigner/resources/seedsigner-translations/l10n
[compile_catalog]
use_fuzzy = yes
+3
View File
@@ -0,0 +1,3 @@
import setuptools
setuptools.setup()
+1 -1
View File
@@ -101,7 +101,7 @@ class Controller(Singleton):
rather than at the top in order avoid circular imports.
"""
VERSION = "0.8.0"
VERSION = "0.8.5"
# Declare class member vars with type hints to enable richer IDE support throughout
# the code.
+5 -2
View File
@@ -154,7 +154,8 @@ class PSBTOverviewScreen(ButtonListScreen):
destination_column.append(_("fee"))
if self.has_op_return:
destination_column.append("OP_RETURN")
# TRANSLATOR_NOTE: Technical term, should probably NOT be translated in most languages
destination_column.append(_("OP_RETURN"))
if self.num_change_outputs > 0:
for i in range(0, self.num_change_outputs):
@@ -731,8 +732,10 @@ class PSBTOpReturnScreen(ButtonListScreen):
text += (decoded_str[i*chars_per_line:(i+1)*chars_per_line]) + "\n"
text = text[:-1]
# TRANSLATOR_NOTE: Shown when displaying OP_RETURN as non-human-readable hexadecimal data
hex_label = _("raw hex data")
label = TextArea(
text="raw hex data",
text=hex_label,
font_color=GUIConstants.LABEL_FONT_COLOR,
font_size=GUIConstants.LABEL_FONT_SIZE,
screen_y=self.top_nav.height,
+2 -2
View File
@@ -57,7 +57,8 @@ class ScanScreen(BaseScreen):
# Initialize the base class
super().__post_init__()
self.instructions_text = _("< back | ") + self.instructions_text
# TODO: Arrange this with UI elements rather than text
self.instructions_text = "< " + _("back") + " | " + _(self.instructions_text)
self.camera = Camera.get_instance()
self.camera.start_video_stream_mode(resolution=self.resolution, framerate=self.framerate, format="rgb")
@@ -209,7 +210,6 @@ class ScanScreen(BaseScreen):
draw.text(
xy=(rectangle.width - GUIConstants.EDGE_PADDING, int(rectangle.height / 2)),
text=text,
# text=f"100%",
fill=GUIConstants.BODY_FONT_COLOR,
font=instructions_font,
anchor="rm", # right-justified, middle
+15 -38
View File
@@ -1,6 +1,6 @@
import time
from dataclasses import dataclass
from dataclasses import dataclass, field
from gettext import gettext as _
from PIL import Image, ImageDraw, ImageColor
from typing import Any, List, Tuple
@@ -206,7 +206,7 @@ class BaseTopNavScreen(BaseScreen):
self.top_nav = TopNav(
icon_name=self.top_nav_icon_name,
icon_color=self.top_nav_icon_color,
text=self.title,
text=_(self.title), # Wrap here for just-in-time translations
font_size=self.title_font_size,
width=self.canvas_width,
height=GUIConstants.TOP_NAV_HEIGHT,
@@ -323,7 +323,7 @@ class ButtonListScreen(BaseTopNavScreen):
(button_label, icon_name, icon_color, button_label_color, right_icon_name) = button_label
button_kwargs = dict(
text=button_label,
text=_(button_label), # Wrap here for just-in-time translations
icon_name=icon_name,
icon_color=icon_color if icon_color else GUIConstants.BUTTON_FONT_COLOR,
is_icon_inline=True,
@@ -565,7 +565,7 @@ class LargeButtonScreen(BaseTopNavScreen):
button_start_x = GUIConstants.EDGE_PADDING + button_width + GUIConstants.COMPONENT_PADDING
button_args = {
"text": button_label,
"text": _(button_label), # Wrap here for just-in-time translations
"screen_x": button_start_x,
"screen_y": button_start_y,
"width": button_width,
@@ -855,24 +855,19 @@ class QRDisplayScreen(BaseScreen):
@dataclass
class LargeIconStatusScreen(ButtonListScreen):
title: str = None
title: str = _("Success!")
status_icon_name: str = SeedSignerIconConstants.SUCCESS
status_icon_size: int = GUIConstants.ICON_PRIMARY_SCREEN_SIZE
status_color: str = GUIConstants.SUCCESS_COLOR
status_headline: str = None
text: str = "" # The body text of the screen
text_edge_padding: int = GUIConstants.EDGE_PADDING
button_data: list = None
button_data: list = field(default_factory=lambda: [_("OK")])
allow_text_overflow: bool = False
def __post_init__(self):
if not self.title:
self.title = _("Success!")
self.is_bottom_list: bool = True
if not self.button_data:
self.button_data = [_("OK")]
self.is_bottom_list = True
super().__post_init__()
self.status_icon = Icon(
@@ -887,7 +882,7 @@ class LargeIconStatusScreen(ButtonListScreen):
next_y = self.status_icon.screen_y + self.status_icon.height + int(GUIConstants.COMPONENT_PADDING/2)
if self.status_headline:
self.warning_headline_textarea = TextArea(
text=self.status_headline,
text=_(self.status_headline), # Wrap here for just-in-time translations
width=self.canvas_width,
screen_y=next_y,
font_color=self.status_color,
@@ -897,7 +892,7 @@ class LargeIconStatusScreen(ButtonListScreen):
self.components.append(TextArea(
height=self.buttons[0].screen_y - next_y,
text=self.text,
text=_(self.text),
width=self.canvas_width,
edge_padding=self.text_edge_padding, # Don't render all the way up to the far left/right edges
screen_y=next_y,
@@ -983,36 +978,19 @@ class WarningEdgesMixin:
@dataclass
class WarningScreen(WarningEdgesMixin, LargeIconStatusScreen):
title: str = None
title: str = _("Caution")
status_icon_name: str = SeedSignerIconConstants.WARNING
status_color: str = "yellow"
status_headline: str = "Privacy Leak!" # The colored text under the alert icon
def __post_init__(self):
if not self.title:
self.title = _("Caution")
if not self.status_headline:
self.status_headline = _("Privacy Leak!")
if not self.button_data:
self.button_data = [_("I Understand")]
super().__post_init__()
status_headline: str = _("Privacy Leak!") # The colored text under the alert icon
button_data: list = field(default_factory=lambda: [_("I Understand")])
@dataclass
class DireWarningScreen(WarningScreen):
status_headline: str = "Classified Info!" # The colored text under the alert icon
status_headline: str = _("Classified Info!") # The colored text under the alert icon
status_color: str = GUIConstants.DIRE_WARNING_COLOR
def __post_init__(self):
if not self.status_headline:
self.status_headline = _("Classified Info!")
super().__post_init__()
@dataclass
@@ -1048,13 +1026,12 @@ class PowerOffScreen(BaseTopNavScreen):
@dataclass
class PowerOffNotRequiredScreen(BaseTopNavScreen):
def __post_init__(self):
self.title = "Just Unplug It"
self.title = _("Just Unplug It")
self.show_back_button = True
super().__post_init__()
text = _("It is safe to disconnect power at any time.")
self.components.append(TextArea(
text=text,
text=_("It is safe to disconnect power at any time."),
screen_y=self.top_nav.height,
height=self.canvas_height - self.top_nav.height,
))
@@ -433,7 +433,6 @@ class SeedFinalizeScreen(ButtonListScreen):
@dataclass
class SeedOptionsScreen(ButtonListScreen):
# Customize defaults
fingerprint: str = None
has_passphrase: bool = False
@@ -309,12 +309,11 @@ class DonateScreen(BaseTopNavScreen):
@dataclass
class SettingsQRConfirmationScreen(ButtonListScreen):
config_name: str = None
title: str = "Settings QR"
status_message: str = "Settings updated..."
title: str = _("Settings QR")
status_message: str = _("Settings updated...")
is_bottom_list: bool = True
def __post_init__(self):
self.title = _("Settings QR")
# Customize defaults
self.button_data = [_("Home")]
self.show_back_button = False
@@ -323,7 +322,7 @@ class SettingsQRConfirmationScreen(ButtonListScreen):
start_y = self.top_nav.height + 20
if self.config_name:
self.config_name_textarea = TextArea(
text=f'"{self.config_name}"',
text=f'"{self.config_name}"', # User-supplied string (from SettingsQR); don't wrap to translate
is_text_centered=True,
auto_line_break=True,
screen_y=start_y
@@ -332,7 +331,7 @@ class SettingsQRConfirmationScreen(ButtonListScreen):
start_y = self.config_name_textarea.screen_y + 50
self.components.append(TextArea(
text=self.status_message,
text=_(self.status_message),
is_text_centered=True,
auto_line_break=True,
screen_y=start_y
+11 -5
View File
@@ -75,7 +75,7 @@ class ToolsImageEntropyLivePreviewScreen(BaseScreen):
int(self.renderer.canvas_width/2),
self.renderer.canvas_height - GUIConstants.EDGE_PADDING
),
text="< back | click joystick",
text="< " + _("back") + " | " + _("click joystick"), # TODO: Render with UI elements instead of text
fill=GUIConstants.BODY_FONT_COLOR,
font=instructions_font,
stroke_width=4,
@@ -100,13 +100,18 @@ class ToolsImageEntropyFinalImageScreen(BaseScreen):
instructions_font = Fonts.get_font(GUIConstants.get_body_font_name(), GUIConstants.get_button_font_size())
self.renderer.canvas.paste(self.final_image)
# TRANSLATOR_NOTE: A prompt to the user to either accept or reshoot the image
reshoot = _("reshoot")
# TRANSLATOR_NOTE: A prompt to the user to either accept or reshoot the image
accept = _("accept")
self.renderer.draw.text(
xy=(
int(self.renderer.canvas_width/2),
self.renderer.canvas_height - GUIConstants.EDGE_PADDING
),
# TRANSLATOR_NOTE: Instructions when reviewing an image; "reshoot" must point left, "accept" must point right
text=_(" < reshoot | accept > "),
text=" < " + reshoot + " | " + accept + " > ",
fill=GUIConstants.BODY_FONT_COLOR,
font=instructions_font,
stroke_width=4,
@@ -253,9 +258,10 @@ class ToolsCalcFinalWordScreen(ButtonListScreen):
# significant n bits always rendered in same column)
discard_selected_bits = "_" * (len(self.checksum_bits))
# TRANSLATOR_NOTE: The additional entropy the user supplied (e.g. coin flips)
your_input = _('Your input: "{}"').format(selection_text)
self.components.append(TextArea(
# TRANSLATOR_NOTE: A labeled presentation of a user-supplied value
text=_('Your input: "{}"').format(selection_text),
text=your_input,
screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING - 2, # Nudge to last line doesn't get too close to "Next" button
height_ignores_below_baseline=True, # Keep the next line (bits display) snugged up, regardless of text rendering below the baseline
))
+27 -16
View File
@@ -57,6 +57,7 @@ class SettingsConstants:
LOCALE__PORTUGUESE = "pt"
LOCALE__RUSSIAN = "ru"
LOCALE__SPANISH = "es"
# Do not wrap for translation; always present each language in its native form
ALL_LOCALES = [
# (LOCALE__ARABIC, "Arabic"),
(LOCALE__CZECH, "čeština"),
@@ -76,10 +77,10 @@ class SettingsConstants:
BTC_DENOMINATION__THRESHOLD = "thr"
BTC_DENOMINATION__BTCSATSHYBRID = "hyb"
ALL_BTC_DENOMINATIONS = [
(BTC_DENOMINATION__BTC, "BTC"),
(BTC_DENOMINATION__SATS, "sats"),
(BTC_DENOMINATION__THRESHOLD, "Threshold at 0.01"),
(BTC_DENOMINATION__BTCSATSHYBRID, "BTC | sats hybrid"),
(BTC_DENOMINATION__BTC, _("BTC")),
(BTC_DENOMINATION__SATS, _("sats")),
(BTC_DENOMINATION__THRESHOLD, _("Threshold at 0.01")),
(BTC_DENOMINATION__BTCSATSHYBRID, _("BTC | sats hybrid")),
]
CAMERA_ROTATION__0 = 0
@@ -97,10 +98,16 @@ class SettingsConstants:
DENSITY__LOW = "L"
DENSITY__MEDIUM = "M"
DENSITY__HIGH = "H"
# TRANSLATOR_NOTE: QR code density option: Low, Medium, High
density_low = _("Low")
# TRANSLATOR_NOTE: QR code density option: Low, Medium, High
density_medium = _("Medium")
# TRANSLATOR_NOTE: QR code density option: Low, Medium, High
density_high = _("High")
ALL_DENSITIES = [
(DENSITY__LOW, _("Low")),
(DENSITY__MEDIUM, _("Medium")),
(DENSITY__HIGH, _("High")),
(DENSITY__LOW, density_low),
(DENSITY__MEDIUM, density_medium),
(DENSITY__HIGH, density_high),
]
# Seed-related constants
@@ -115,6 +122,7 @@ class SettingsConstants:
@classmethod
def map_network_to_embit(cls, network) -> str:
# Note these are `embit` constants; do not wrap for translation
if network == SettingsConstants.MAINNET:
return "main"
elif network == SettingsConstants.TESTNET:
@@ -138,11 +146,11 @@ class SettingsConstants:
TAPROOT = "tr"
CUSTOM_DERIVATION = "cus"
ALL_SCRIPT_TYPES = [
(NATIVE_SEGWIT, "Native Segwit"),
(NESTED_SEGWIT, "Nested Segwit"),
(LEGACY_P2PKH, "Legacy"),
(TAPROOT, "Taproot"),
(CUSTOM_DERIVATION, "Custom Derivation"),
(NATIVE_SEGWIT, _("Native Segwit")),
(NESTED_SEGWIT, _("Nested Segwit")),
(LEGACY_P2PKH, _("Legacy")),
(TAPROOT, _("Taproot")),
(CUSTOM_DERIVATION, _("Custom Derivation")),
]
WORDLIST_LANGUAGE__ENGLISH = "en"
@@ -166,6 +174,7 @@ class SettingsConstants:
# Individual SettingsEntry attr_names
# Note: attr_names are internal constants; do not wrap for translation
SETTING__LOCALE = "locale"
SETTING__WORDLIST_LANGUAGE = "wordlist_language"
SETTING__PERSISTENT_SETTINGS = "persistent_settings"
@@ -228,8 +237,10 @@ class SettingsConstants:
ELECTRUM_PBKDF2_ROUNDS=2048
# Label strings
LABEL__BIP39_PASSPHRASE = "BIP-39 Passphrase"
LABEL__CUSTOM_EXTENSION = "Custom Extension" # Terminology used by Electrum seeds
LABEL__BIP39_PASSPHRASE = _("BIP-39 Passphrase")
# TRANSLATOR_NOTE: Terminology used by Electrum seeds; equivalent to bip39 passphrase
custom_extension = _("Custom Extension")
LABEL__CUSTOM_EXTENSION = custom_extension
@@ -368,8 +379,8 @@ class SettingsDefinition:
SettingsEntry(category=SettingsConstants.CATEGORY__SYSTEM,
attr_name=SettingsConstants.SETTING__LOCALE,
display_name=_("Language"),
abbreviated_name="lang",
display_name=_("Language"),
type=SettingsConstants.TYPE__SELECT_1,
selection_options=SettingsConstants.ALL_LOCALES,
default_value=SettingsConstants.LOCALE__ENGLISH),
@@ -494,7 +505,7 @@ class SettingsDefinition:
attr_name=SettingsConstants.SETTING__ELECTRUM_SEEDS,
abbreviated_name="electrum",
display_name="Electrum seeds",
help_text="Native Segwit only",
help_text=_("Native Segwit only"),
visibility=SettingsConstants.VISIBILITY__ADVANCED,
default_value=SettingsConstants.OPTION__DISABLED),
+10 -17
View File
@@ -15,11 +15,10 @@ from seedsigner.views.view import BackStackView, MainMenuView, NotYetImplemented
class PSBTSelectSeedView(View):
# TODO: Class-level attrs vs dynamic gettext calls
SCAN_SEED = ("Scan a seed", SeedSignerIconConstants.QRCODE)
TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_ELECTRUM = ("Enter Electrum seed", FontAwesomeIconConstants.KEYBOARD)
SCAN_SEED = (_("Scan a seed"), SeedSignerIconConstants.QRCODE)
TYPE_12WORD = (_("Enter 12-word seed"), FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = (_("Enter 24-word seed"), FontAwesomeIconConstants.KEYBOARD)
TYPE_ELECTRUM = (_("Enter Electrum seed"), FontAwesomeIconConstants.KEYBOARD)
def run(self):
@@ -29,9 +28,6 @@ class PSBTSelectSeedView(View):
# Shouldn't be able to get here
raise Exception("No PSBT currently loaded")
self.SCAN_SEED = (_("Scan a seed"), SeedSignerIconConstants.QRCODE)
self.TYPE_12WORD = (_("Enter 12-word seed"), FontAwesomeIconConstants.KEYBOARD)
self.TYPE_24WORD = (_("Enter 24-word seed"), FontAwesomeIconConstants.KEYBOARD)
if self.controller.psbt_seed:
if PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=self.controller.psbt_seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
# skip the seed prompt if a seed was previous selected and has matching input fingerprint
@@ -43,7 +39,8 @@ class PSBTSelectSeedView(View):
button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
if not PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
# Doesn't look like this seed can sign the current PSBT
button_str += " (?)"
# TRANSLATOR_NOTE: Inserts fingerprint w/"?" to indicate that this seed can't sign the current PSBT
button_str = _("{} (?)").format(button_str)
button_data.append((button_str, SeedSignerIconConstants.FINGERPRINT))
@@ -303,9 +300,9 @@ class PSBTAddressDetailsView(View):
class PSBTChangeDetailsView(View):
NEXT = "Next"
SKIP_VERIFICATION = "Skip Verification"
VERIFY_MULTISIG = "Verify Multisig Change"
NEXT = _("Next")
SKIP_VERIFICATION = _("Skip Verification")
VERIFY_MULTISIG = _("Verify Multisig Change")
def __init__(self, change_address_num):
@@ -347,12 +344,9 @@ class PSBTChangeDetailsView(View):
is_change_derivation_path = int(derivation_path.split("/")[-2]) == 1
derivation_path_addr_index = int(derivation_path.split("/")[-1])
self.NEXT = _("Next")
if is_change_derivation_path:
# TRANSLATOR_NOTE: The amount you're receiving back from the transaction
title = _("Your Change")
self.VERIFY_MULTISIG = _("Verify Multisig Change")
else:
title = _("Self-Transfer")
self.VERIFY_MULTISIG = _("Verify Multisig Addr")
@@ -569,11 +563,10 @@ class PSBTSignedQRDisplayView(View):
class PSBTSigningErrorView(View):
SELECT_DIFF_SEED = "Select Diff Seed"
SELECT_DIFF_SEED = _("Select Diff Seed")
def run(self):
psbt_parser: PSBTParser = self.controller.psbt_parser
self.SELECT_DIFF_SEED = _("Select Diff Seed")
if not psbt_parser:
# Should not be able to get here
return Destination(MainMenuView)
+2 -1
View File
@@ -62,10 +62,11 @@ class ScanView(View):
# current flow.
# Report QR types in more human-readable text (e.g. QRType
# `seed__compactseedqr` as "seed: compactseedqr").
# TODO: cleanup l10n presentation
return Destination(ErrorView, view_args=dict(
title="Error",
status_headline=_("Wrong QR Type"),
text=self.invalid_qr_type_message + f""", received "{self.decoder.qr_type.replace("__", ": ").replace("_", " ")}\" format""",
text=_(self.invalid_qr_type_message) + f""", received "{self.decoder.qr_type.replace("__", ": ").replace("_", " ")}\" format""",
button_text="Back",
next_destination=Destination(BackStackView, skip_current_view=True),
))
+84 -130
View File
@@ -78,10 +78,10 @@ class SeedSelectSeedView(View):
* `flow`: indicates which user flow is in progress during seed selection (e.g.
verify single sig addr or sign message).
"""
SCAN_SEED = ("Scan a seed", SeedSignerIconConstants.QRCODE)
TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_ELECTRUM = ("Enter Electrum seed", FontAwesomeIconConstants.KEYBOARD)
SCAN_SEED = (_("Scan a seed"), SeedSignerIconConstants.QRCODE)
TYPE_12WORD = (_("Enter 12-word seed"), FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = (_("Enter 24-word seed"), FontAwesomeIconConstants.KEYBOARD)
TYPE_ELECTRUM = (_("Enter Electrum seed"), FontAwesomeIconConstants.KEYBOARD)
def __init__(self, flow: str = Controller.FLOW__VERIFY_SINGLESIG_ADDR):
@@ -90,9 +90,6 @@ class SeedSelectSeedView(View):
def run(self):
self.SCAN_SEED = (_("Scan a seed"), SeedSignerIconConstants.QRCODE)
self.TYPE_12WORD = (_("Enter 12-word seed"), FontAwesomeIconConstants.KEYBOARD)
self.TYPE_24WORD = (_("Enter 24-word seed"), FontAwesomeIconConstants.KEYBOARD)
seeds = self.controller.storage.seeds
if self.flow == Controller.FLOW__VERIFY_SINGLESIG_ADDR:
@@ -172,17 +169,13 @@ class SeedSelectSeedView(View):
Loading seeds, passphrases, etc
****************************************************************************"""
class LoadSeedView(View):
SEED_QR = ("Scan a SeedQR", SeedSignerIconConstants.QRCODE)
TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_ELECTRUM = ("Enter Electrum seed", FontAwesomeIconConstants.KEYBOARD)
CREATE = (" Create a seed", SeedSignerIconConstants.PLUS)
SEED_QR = (_("Scan a SeedQR"), SeedSignerIconConstants.QRCODE)
TYPE_12WORD = (_("Enter 12-word seed"), FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = (_("Enter 24-word seed"), FontAwesomeIconConstants.KEYBOARD)
TYPE_ELECTRUM = (_("Enter Electrum seed"), FontAwesomeIconConstants.KEYBOARD)
CREATE = (_(" Create a seed"), SeedSignerIconConstants.PLUS)
def run(self):
self.SEED_QR = (_("Scan a SeedQR"), SeedSignerIconConstants.QRCODE)
self.TYPE_12WORD = (_("Enter 12-word seed"), FontAwesomeIconConstants.KEYBOARD)
self.TYPE_24WORD = (_("Enter 24-word seed"), FontAwesomeIconConstants.KEYBOARD)
self.CREATE = (_("Create a seed"), SeedSignerIconConstants.PLUS)
button_data = [
self.SEED_QR,
self.TYPE_12WORD,
@@ -284,8 +277,8 @@ class SeedMnemonicEntryView(View):
class SeedMnemonicInvalidView(View):
EDIT = "Review & Edit"
DISCARD = ("Discard", None, None, "red")
EDIT = _("Review & Edit")
DISCARD = (_("Discard"), None, None, "red")
def __init__(self):
super().__init__()
@@ -293,8 +286,6 @@ class SeedMnemonicInvalidView(View):
def run(self):
self.EDIT = _("Review & Edit")
self.DISCARD = (_("Discard"), None, None, "red")
button_data = [self.EDIT, self.DISCARD]
selected_menu_num = self.run_screen(
WarningScreen,
@@ -315,7 +306,8 @@ class SeedMnemonicInvalidView(View):
class SeedFinalizeView(View):
FINALIZE = "Done"
FINALIZE = _("Done")
PASSPHRASE = _("BIP-39 Passphrase")
def __init__(self):
super().__init__()
@@ -337,9 +329,6 @@ class SeedFinalizeView(View):
def run(self):
self.FINALIZE = _("Done")
self.PASSPHRASE = _("BIP-39 Passphrase")
button_data = [self.FINALIZE]
passphrase_button = self.seed.passphrase_label
if self.settings.get_value(SettingsConstants.SETTING__PASSPHRASE) != SettingsConstants.OPTION__DISABLED:
@@ -391,8 +380,8 @@ class SeedAddPassphraseView(View):
class SeedAddPassphraseExitDialogView(View):
EDIT = "Edit passphrase"
DISCARD = ("Discard passphrase", None, None, "red")
EDIT = _("Edit passphrase")
DISCARD = (_("Discard passphrase"), None, None, "red")
def __init__(self):
super().__init__()
@@ -404,9 +393,9 @@ class SeedAddPassphraseExitDialogView(View):
selected_menu_num = self.run_screen(
WarningScreen,
title="Discard passphrase?",
title=_("Discard passphrase?"),
status_headline=None,
text=f"Your current passphrase entry will be erased",
text=_("Your current passphrase entry will be erased"),
show_back_button=False,
button_data=button_data,
)
@@ -424,8 +413,8 @@ class SeedReviewPassphraseView(View):
"""
Display the completed passphrase back to the user.
"""
EDIT = "Edit passphrase"
DONE = "Done"
EDIT = _("Edit passphrase")
DONE = _("Done")
def __init__(self):
super().__init__()
@@ -433,9 +422,6 @@ class SeedReviewPassphraseView(View):
def run(self):
self.EDIT = _("Edit passphrase")
self.DONE = _("Done")
# Get the before/after fingerprints
network = self.settings.get_value(SettingsConstants.SETTING__NETWORK)
passphrase = self.seed.passphrase
@@ -467,8 +453,8 @@ class SeedReviewPassphraseView(View):
class SeedDiscardView(View):
KEEP = "Keep Seed"
DISCARD = ("Discard", None, None, "red")
KEEP = _("Keep Seed")
DISCARD = (_("Discard"), None, None, "red")
def __init__(self, seed_num: int = None):
super().__init__()
@@ -480,9 +466,6 @@ class SeedDiscardView(View):
def run(self):
self.KEEP = _("Keep Seed")
self.DISCARD = (_("Discard"), None, None, "red")
button_data = [self.KEEP, self.DISCARD]
fingerprint = self.seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
@@ -522,9 +505,9 @@ class SeedElectrumMnemonicStartView(View):
def run(self):
self.run_screen(
WarningScreen,
title="Electrum warning",
title=_("Electrum warning"),
status_headline=None,
text=f"Some features are disabled for Electrum seeds.",
text=_("Some features are disabled for Electrum seeds."),
show_back_button=False,
)
@@ -538,14 +521,14 @@ class SeedElectrumMnemonicStartView(View):
Views for actions on individual seeds:
****************************************************************************"""
class SeedOptionsView(View):
SCAN_PSBT = ("Scan PSBT", SeedSignerIconConstants.QRCODE)
VERIFY_ADDRESS = "Verify Addr"
EXPORT_XPUB = "Export Xpub"
EXPLORER = "Address Explorer"
SIGN_MESSAGE = "Sign Message"
BACKUP = ("Backup Seed", None, None, None, SeedSignerIconConstants.CHEVRON_RIGHT)
BIP85_CHILD_SEED = "BIP-85 Child Seed"
DISCARD = ("Discard Seed", None, None, "red")
SCAN_PSBT = (_("Scan PSBT"), SeedSignerIconConstants.QRCODE)
VERIFY_ADDRESS = _("Verify Addr")
EXPORT_XPUB = _("Export Xpub")
EXPLORER = _("Address Explorer")
SIGN_MESSAGE = _("Sign Message")
BACKUP = (_("Backup Seed"), None, None, None, SeedSignerIconConstants.CHEVRON_RIGHT)
BIP85_CHILD_SEED = _("BIP-85 Child Seed")
DISCARD = (_("Discard Seed"), None, None, "red")
def __init__(self, seed_num: int):
@@ -557,13 +540,6 @@ class SeedOptionsView(View):
def run(self):
from seedsigner.views.psbt_views import PSBTOverviewView
self.SCAN_PSBT = (_("Scan PSBT"), SeedSignerIconConstants.QRCODE)
self.VERIFY_ADDRESS = _("Verify Addr")
self.EXPORT_XPUB = _("Export Xpub")
self.EXPLORER = _("Address Explorer")
self.BACKUP = (_("Backup Seed"), None, None, None, SeedSignerIconConstants.CHEVRON_RIGHT)
self.BIP85_CHILD_SEED = _("BIP-85 Child Seed")
self.DISCARD = (_("Discard Seed"), None, None, "red")
if self.controller.unverified_address:
if self.controller.resume_main_flow == Controller.FLOW__VERIFY_SINGLESIG_ADDR:
# Jump straight back into the single sig addr verification flow
@@ -655,8 +631,8 @@ class SeedOptionsView(View):
class SeedBackupView(View):
VIEW_WORDS = "View Seed Words"
EXPORT_SEEDQR = "Export as SeedQR"
VIEW_WORDS = _("View Seed Words")
EXPORT_SEEDQR = _("Export as SeedQR")
def __init__(self, seed_num):
super().__init__()
@@ -665,8 +641,6 @@ class SeedBackupView(View):
def run(self):
self.VIEW_WORDS = _("View Seed Words")
self.EXPORT_SEEDQR = _("Export as SeedQR")
button_data = [self.VIEW_WORDS]
if self.seed.seedqr_supported:
@@ -694,8 +668,8 @@ class SeedBackupView(View):
Export Xpub flow
****************************************************************************"""
class SeedExportXpubSigTypeView(View):
SINGLE_SIG = "Single Sig"
MULTISIG = "Multisig"
SINGLE_SIG = _("Single Sig")
MULTISIG = _("Multisig")
def __init__(self, seed_num: int):
super().__init__()
@@ -707,8 +681,6 @@ class SeedExportXpubSigTypeView(View):
# Nothing to select; skip this screen
return Destination(SeedExportXpubScriptTypeView, view_args={"seed_num": self.seed_num, "sig_type": self.settings.get_value(SettingsConstants.SETTING__SIG_TYPES)[0]}, skip_current_view=True)
self.SINGLE_SIG = _("Single Sig")
self.MULTISIG = _("Multisig")
button_data=[self.SINGLE_SIG, self.MULTISIG]
selected_menu_num = self.run_screen(
@@ -765,7 +737,7 @@ class SeedExportXpubScriptTypeView(View):
button_data = []
for script_type, display_name in SettingsConstants.ALL_SCRIPT_TYPES:
if script_type in self.settings.get_value(SettingsConstants.SETTING__SCRIPT_TYPES):
button_data.append((script_type, _(display_name)))
button_data.append((script_type, display_name))
selected_menu_num = self.run_screen(
ButtonListScreen,
@@ -1071,6 +1043,9 @@ class SeedWordsWarningView(View):
class SeedWordsView(View):
NEXT = _("Next")
DONE = _("Done")
def __init__(self, seed_num: int, bip85_data: dict = None, page_index: int = 0):
super().__init__()
self.seed_num = seed_num
@@ -1083,9 +1058,6 @@ class SeedWordsView(View):
def run(self):
NEXT = _("Next")
DONE = _("Done")
# Slice the mnemonic to our current 4-word section
words_per_page = 4 # TODO: eventually make this configurable for bigger screens?
@@ -1101,9 +1073,9 @@ class SeedWordsView(View):
button_data = []
num_pages = int(len(mnemonic)/words_per_page)
if self.page_index < num_pages - 1 or self.seed_num is None:
button_data.append(NEXT)
button_data.append(self.NEXT)
else:
button_data.append(DONE)
button_data.append(self.DONE)
selected_menu_num = seed_screens.SeedWordsScreen(
title=f"{title}: {self.page_index+1}/{num_pages}",
@@ -1116,7 +1088,7 @@ class SeedWordsView(View):
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
if button_data[selected_menu_num] == NEXT:
if button_data[selected_menu_num] == self.NEXT:
if self.seed_num is None and self.page_index == num_pages - 1:
return Destination(
SeedWordsBackupTestPromptView,
@@ -1128,7 +1100,7 @@ class SeedWordsView(View):
view_args=dict(seed_num=self.seed_num, page_index=self.page_index + 1, bip85_data=self.bip85_data)
)
elif button_data[selected_menu_num] == DONE:
elif button_data[selected_menu_num] == self.DONE:
# Must clear history to avoid BACK button returning to private info
return Destination(
SeedWordsBackupTestPromptView,
@@ -1148,6 +1120,10 @@ class SeedBIP85ApplicationModeView(View):
* WIF (HDSEED)
* XPRV (BIP32)
"""
# TODO: Future enhancement to display WIF (HD-SEED) and XPRV (Bip32)?
WORDS_12 = _("12 Words")
WORDS_24 = _("24 Words")
def __init__(self, seed_num: int):
super().__init__()
self.seed_num = seed_num
@@ -1156,11 +1132,7 @@ class SeedBIP85ApplicationModeView(View):
def run(self):
# TODO: Future enhancement to display WIF (HD-SEED) and XPRV (Bip32)?
WORDS_12 = _("12 Words")
WORDS_24 = _("24 Words")
button_data = [WORDS_12, WORDS_24]
button_data = [self.WORDS_12, self.WORDS_24]
selected_menu_num = ButtonListScreen(
title=_("BIP-85 Num Words"),
@@ -1170,9 +1142,9 @@ class SeedBIP85ApplicationModeView(View):
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
if button_data[selected_menu_num] == WORDS_12:
if button_data[selected_menu_num] == self.WORDS_12:
self.num_words = 12
elif button_data[selected_menu_num] == WORDS_24:
elif button_data[selected_menu_num] == self.WORDS_24:
self.num_words = 24
return Destination(
@@ -1247,6 +1219,9 @@ class SeedBIP85InvalidChildIndexView(View):
Seed Words Backup Test
****************************************************************************"""
class SeedWordsBackupTestPromptView(View):
VERIFY = _("Verify")
SKIP = _("Skip")
def __init__(self, seed_num: int, bip85_data: dict = None):
super().__init__()
self.seed_num = seed_num
@@ -1254,20 +1229,18 @@ class SeedWordsBackupTestPromptView(View):
def run(self):
VERIFY = _("Verify")
SKIP = _("Skip")
button_data = [VERIFY, SKIP]
button_data = [self.VERIFY, self.SKIP]
selected_menu_num = seed_screens.SeedWordsBackupTestPromptScreen(
button_data=button_data,
).display()
if button_data[selected_menu_num] == VERIFY:
if button_data[selected_menu_num] == self.VERIFY:
return Destination(
SeedWordsBackupTestView,
view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data),
)
elif button_data[selected_menu_num] == SKIP:
elif button_data[selected_menu_num] == self.SKIP:
if self.seed_num is not None:
return Destination(SeedOptionsView, view_args=dict(seed_num=self.seed_num))
else:
@@ -1352,6 +1325,9 @@ class SeedWordsBackupTestView(View):
class SeedWordsBackupTestMistakeView(View):
REVIEW = _("Review Seed Words")
RETRY = _("Try Again")
def __init__(self, seed_num: int, bip85_data: dict = None, cur_index: int = None, wrong_word: str = None, confirmed_list: List[bool] = None):
super().__init__()
self.seed_num = seed_num
@@ -1362,9 +1338,7 @@ class SeedWordsBackupTestMistakeView(View):
def run(self):
REVIEW = _("Review Seed Words")
RETRY = _("Try Again")
button_data = [REVIEW, RETRY]
button_data = [self.REVIEW, self.RETRY]
# TRANSLATOR_NOTE: Inserts the word number and the word (e.g. "Word #1 is not "apple"!")
text = _("Word #{} is not \"{}\"!").format(self.cur_index + 1, self.wrong_word)
@@ -1380,13 +1354,13 @@ class SeedWordsBackupTestMistakeView(View):
text=text,
).display()
if button_data[selected_menu_num] == REVIEW:
if button_data[selected_menu_num] == self.REVIEW:
return Destination(
SeedWordsView,
view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data),
)
elif button_data[selected_menu_num] == RETRY:
elif button_data[selected_menu_num] == self.RETRY:
return Destination(
SeedWordsBackupTestView,
view_args=dict(
@@ -1598,6 +1572,9 @@ class SeedTranscribeSeedQRZoomedInView(View):
class SeedTranscribeSeedQRConfirmQRPromptView(View):
SCAN = (_("Confirm SeedQR"), SeedSignerIconConstants.QRCODE)
DONE = _("Done")
def __init__(self, seed_num: int):
super().__init__()
self.seed_num = seed_num
@@ -1605,9 +1582,7 @@ class SeedTranscribeSeedQRConfirmQRPromptView(View):
def run(self):
SCAN = (_("Confirm SeedQR"), SeedSignerIconConstants.QRCODE)
DONE = _("Done")
button_data = [SCAN, DONE]
button_data = [self.SCAN, self.DONE]
selected_menu_option = seed_screens.SeedTranscribeSeedQRConfirmQRPromptScreen(
title=_("Confirm SeedQR?"),
@@ -1617,10 +1592,10 @@ class SeedTranscribeSeedQRConfirmQRPromptView(View):
if selected_menu_option == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
elif button_data[selected_menu_option] == SCAN:
elif button_data[selected_menu_option] == self.SCAN:
return Destination(SeedTranscribeSeedQRConfirmScanView, view_args={"seed_num": self.seed_num})
elif button_data[selected_menu_option] == DONE:
elif button_data[selected_menu_option] == self.DONE:
return Destination(SeedOptionsView, view_args={"seed_num": self.seed_num}, clear_history=True)
@@ -1739,13 +1714,8 @@ class AddressVerificationStartView(View):
class AddressVerificationSigTypeView(View):
SINGLE_SIG = "Single Sig"
MULTISIG = "Multisig"
def __post_init__(self):
self.SINGLE_SIG = _("Single Sig")
self.MULTISIG = _("Multisig")
return super().__post_init__()
SINGLE_SIG = _("Single Sig")
MULTISIG = _("Multisig")
def run(self):
button_data = [self.SINGLE_SIG, self.MULTISIG]
@@ -1796,6 +1766,10 @@ class SeedAddressVerificationView(View):
Performs single sig verification on `seed_num` if specified, otherwise assumes
multisig.
"""
# TRANSLATOR_NOTE: Option when scanning for a matching address; skips ten addresses ahead
SKIP_10 = _("Skip 10")
CANCEL = _("Cancel")
def __init__(self, seed_num: int = None):
super().__init__()
self.seed_num = seed_num
@@ -1849,10 +1823,7 @@ class SeedAddressVerificationView(View):
# Start brute-force calculations from the zero-th index
self.addr_verification_thread.start()
# TRANSLATOR_NOTE: Option when scanning for a matching address; skips ten addresses ahead
SKIP_10 = _("Skip 10")
CANCEL = _("Cancel")
button_data = [SKIP_10, CANCEL]
button_data = [self.SKIP_10, self.CANCEL]
script_type_settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__SCRIPT_TYPES)
script_type_display = script_type_settings_entry.get_selection_option_display_name_by_value(self.script_type)
@@ -1894,10 +1865,10 @@ class SeedAddressVerificationView(View):
time.sleep(0.1)
continue
if button_data[selected_menu_num] == SKIP_10:
if button_data[selected_menu_num] == self.SKIP_10:
self.threadsafe_counter.increment(10)
elif button_data[selected_menu_num] == CANCEL:
elif button_data[selected_menu_num] == self.CANCEL:
break
if self.verified_index.cur_count is not None:
@@ -2013,20 +1984,11 @@ class AddressVerificationSuccessView(View):
class LoadMultisigWalletDescriptorView(View):
SCAN = ("Scan Descriptor", SeedSignerIconConstants.QRCODE)
CANCEL = "Cancel"
def __post_init__(self):
self.SCAN = (_("Scan Descriptor"), SeedSignerIconConstants.QRCODE)
self.CANCEL = _("Cancel")
return super().__post_init__()
SCAN = (_("Scan Descriptor"), SeedSignerIconConstants.QRCODE)
CANCEL = _("Cancel")
def run(self):
button_data = [
self.SCAN,
self.CANCEL
]
button_data = [self.SCAN, self.CANCEL]
selected_menu_num = self.run_screen(
seed_screens.LoadMultisigWalletDescriptorScreen,
button_data=button_data,
@@ -2046,18 +2008,10 @@ class LoadMultisigWalletDescriptorView(View):
class MultisigWalletDescriptorView(View):
RETURN = "Return to PSBT"
VERIFY_ADDR = "Verify Addr"
ADDRESS_EXPLORER = "Address Explorer"
OK = "OK"
def __post_init__(self):
self.RETURN = _("Return to PSBT")
self.VERIFY = _("Verify Addr")
self.EXPLORER = _("Address Explorer")
self.OK = _("OK")
super().__post_init__()
RETURN = _("Return to PSBT")
VERIFY_ADDR = _("Verify Addr")
ADDRESS_EXPLORER = _("Address Explorer")
OK = _("OK")
def run(self):
descriptor = self.controller.multisig_wallet_descriptor
+4 -7
View File
@@ -16,8 +16,8 @@ from .view import View, Destination, BackStackView, MainMenuView
class SettingsMenuView(View):
IO_TEST = "I/O test"
DONATE = "Donate"
IO_TEST = _("I/O test")
DONATE = _("Donate")
def __init__(self, visibility: str = SettingsConstants.VISIBILITY__GENERAL, selected_attr: str = None, initial_scroll: int = 0):
super().__init__()
@@ -29,13 +29,10 @@ class SettingsMenuView(View):
def run(self):
self.IO_TEST = _("I/O test")
self.DONATE = _("Donate")
settings_entries = SettingsDefinition.get_settings_entries(
visibility=self.visibility
)
button_data=[_(e.display_name) for e in settings_entries]
button_data=[e.display_name for e in settings_entries]
selected_button = 0
if self.selected_attr:
@@ -121,7 +118,7 @@ class SettingsEntryUpdateSelectionView(View):
value, display_name = value
else:
display_name = value
button_data.append(_(display_name))
button_data.append(display_name)
if (type(initial_value) == list and value in initial_value) or value == initial_value:
checked_buttons.append(i)
+56 -69
View File
@@ -27,20 +27,11 @@ logger = logging.getLogger(__name__)
class ToolsMenuView(View):
IMAGE = ("New seed", FontAwesomeIconConstants.CAMERA)
DICE = ("New seed", FontAwesomeIconConstants.DICE)
KEYBOARD = ("Calc 12th/24th word", FontAwesomeIconConstants.KEYBOARD)
ADDRESS_EXPLORER = "Address Explorer"
VERIFY_ADDRESS = "Verify address"
def __post_init__(self):
self.IMAGE = (_("New seed"), FontAwesomeIconConstants.CAMERA)
self.DICE = (_("New seed"), FontAwesomeIconConstants.DICE)
self.KEYBOARD = (_("Calc 12th/24th word"), FontAwesomeIconConstants.KEYBOARD)
self.ADDRESS_EXPLORER = _("Address Explorer")
self.ADDRESS = _("Verify Address")
super().__post_init__()
IMAGE = (_("New seed"), FontAwesomeIconConstants.CAMERA)
DICE = (_("New seed"), FontAwesomeIconConstants.DICE)
KEYBOARD = (_("Calc 12th/24th word"), FontAwesomeIconConstants.KEYBOARD)
ADDRESS_EXPLORER = _("Address Explorer")
VERIFY_ADDRESS = _("Verify Address")
def run(self):
button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.ADDRESS_EXPLORER, self.VERIFY_ADDRESS]
@@ -126,10 +117,11 @@ class ToolsImageEntropyFinalImageView(View):
class ToolsImageEntropyMnemonicLengthView(View):
TWELVE_WORDS = _("12 words")
TWENTYFOUR_WORDS = _("24 words")
def run(self):
TWELVE_WORDS = _("12 words")
TWENTYFOUR_WORDS = _("24 words")
button_data = [TWELVE_WORDS, TWENTYFOUR_WORDS]
button_data = [self.TWELVE_WORDS, self.TWENTYFOUR_WORDS]
selected_menu_num = ButtonListScreen(
title=_("Mnemonic Length?"),
@@ -139,7 +131,7 @@ class ToolsImageEntropyMnemonicLengthView(View):
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
if button_data[selected_menu_num] == TWELVE_WORDS:
if button_data[selected_menu_num] == self.TWELVE_WORDS:
mnemonic_length = 12
else:
mnemonic_length = 24
@@ -198,14 +190,15 @@ class ToolsImageEntropyMnemonicLengthView(View):
Dice rolls Views
****************************************************************************"""
class ToolsDiceEntropyMnemonicLengthView(View):
def run(self):
# TRANSLATOR_NOTE: Inserts the number of dice rolls needed for a 12-word mnemonic
TWELVE = _("12 words ({} rolls)").format(mnemonic_generation.DICE__NUM_ROLLS__12WORD)
# TRANSLATOR_NOTE: Inserts the number of dice rolls needed for a 12-word mnemonic
TWELVE = _("12 words ({} rolls)").format(mnemonic_generation.DICE__NUM_ROLLS__12WORD)
# TRANSLATOR_NOTE: Inserts the number of dice rolls needed for a 24-word mnemonic
TWENTY_FOUR = _("24 words ({} rolls)").format(mnemonic_generation.DICE__NUM_ROLLS__24WORD)
button_data = [TWELVE, TWENTY_FOUR]
# TRANSLATOR_NOTE: Inserts the number of dice rolls needed for a 24-word mnemonic
TWENTY_FOUR = _("24 words ({} rolls)").format(mnemonic_generation.DICE__NUM_ROLLS__24WORD)
def run(self):
button_data = [self.TWELVE, self.TWENTY_FOUR]
selected_menu_num = ButtonListScreen(
title=_("Mnemonic Length"),
is_bottom_list=True,
@@ -216,10 +209,10 @@ class ToolsDiceEntropyMnemonicLengthView(View):
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
elif button_data[selected_menu_num] == TWELVE:
elif button_data[selected_menu_num] == self.TWELVE:
return Destination(ToolsDiceEntropyEntryView, view_args=dict(total_rolls=mnemonic_generation.DICE__NUM_ROLLS__12WORD))
elif button_data[selected_menu_num] == TWENTY_FOUR:
elif button_data[selected_menu_num] == self.TWENTY_FOUR:
return Destination(ToolsDiceEntropyEntryView, view_args=dict(total_rolls=mnemonic_generation.DICE__NUM_ROLLS__24WORD))
@@ -253,13 +246,10 @@ class ToolsDiceEntropyEntryView(View):
Calc final word Views
****************************************************************************"""
class ToolsCalcFinalWordNumWordsView(View):
TWELVE = "12 words"
TWENTY_FOUR = "24 words"
TWELVE = _("12 words")
TWENTY_FOUR = _("24 words")
def run(self):
self.TWELVE = _("12 words")
self.TWENTY_FOUR = _("24 words")
button_data = [self.TWELVE, self.TWENTY_FOUR]
selected_menu_num = self.run_screen(
@@ -288,6 +278,15 @@ class ToolsCalcFinalWordNumWordsView(View):
class ToolsCalcFinalWordFinalizePromptView(View):
# TRANSLATOR_NOTE: Label to gather entropy through coin tosses
COIN_FLIPS = _("Coin flip entropy")
# TRANSLATOR_NOTE: Label to gather entropy through user specified BIP-39 word
SELECT_WORD = _("Word selection entropy")
# TRANSLATOR_NOTE: Label to allow user to default entropy as all-zeros
ZEROS = _("Finalize with zeros")
def run(self):
mnemonic = self.controller.storage.pending_mnemonic
mnemonic_length = len(mnemonic)
@@ -296,14 +295,7 @@ class ToolsCalcFinalWordFinalizePromptView(View):
else:
num_entropy_bits = 3
# TRANSLATOR_NOTE: Label to gather entropy through coin tosses
COIN_FLIPS = _("Coin flip entropy")
# TRANSLATOR_NOTE: Label to gather entropy through user specified BIP-39 word
SELECT_WORD = _("Word selection entropy")
# TRANSLATOR_NOTE: Label to allow user to default entropy as all-zeros
ZEROS = _("Finalize with zeros")
button_data = [COIN_FLIPS, SELECT_WORD, ZEROS]
button_data = [self.COIN_FLIPS, self.SELECT_WORD, self.ZEROS]
selected_menu_num = ToolsCalcFinalWordFinalizePromptScreen(
mnemonic_length=mnemonic_length,
num_entropy_bits=num_entropy_bits,
@@ -313,15 +305,15 @@ class ToolsCalcFinalWordFinalizePromptView(View):
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
elif button_data[selected_menu_num] == COIN_FLIPS:
elif button_data[selected_menu_num] == self.COIN_FLIPS:
return Destination(ToolsCalcFinalWordCoinFlipsView)
elif button_data[selected_menu_num] == SELECT_WORD:
elif button_data[selected_menu_num] == self.SELECT_WORD:
# Clear the final word slot, just in case we're returning via BACK button
self.controller.storage.update_pending_mnemonic(None, mnemonic_length - 1)
return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True, cur_word_index=mnemonic_length - 1))
elif button_data[selected_menu_num] == ZEROS:
elif button_data[selected_menu_num] == self.ZEROS:
# User skipped the option to select a final word to provide last bits of
# entropy. We'll insert all zeros and piggy-back on the coin flip attr
wordlist_language_code = self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE)
@@ -352,6 +344,8 @@ class ToolsCalcFinalWordCoinFlipsView(View):
class ToolsCalcFinalWordShowFinalWordView(View):
NEXT = _("Next")
def __init__(self, coin_flips: str = None):
super().__init__()
# Construct the actual final word. The user's selected_final_word
@@ -404,9 +398,7 @@ class ToolsCalcFinalWordShowFinalWordView(View):
def run(self):
# l10n_note already exists.
NEXT = _("Next")
button_data = [NEXT]
button_data = [self.NEXT]
selected_menu_num = self.run_screen(
ToolsCalcFinalWordScreen,
# TRANSLATOR_NOTE: label to calculate the last word of a BIP-39 mnemonic seed phrase
@@ -421,20 +413,21 @@ class ToolsCalcFinalWordShowFinalWordView(View):
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
elif button_data[selected_menu_num] == NEXT:
elif button_data[selected_menu_num] == self.NEXT:
return Destination(ToolsCalcFinalWordDoneView)
class ToolsCalcFinalWordDoneView(View):
LOAD = _("Load seed")
DISCARD = (_("Discard"), None, None, "red")
def run(self):
mnemonic = self.controller.storage.pending_mnemonic
mnemonic_word_length = len(mnemonic)
final_word = mnemonic[-1]
LOAD = _("Load seed")
DISCARD = (_("Discard"), None, None, "red")
button_data = [LOAD, DISCARD]
button_data = [self.LOAD, self.DISCARD]
selected_menu_num = ToolsCalcFinalWordDoneScreen(
final_word=final_word,
@@ -448,30 +441,26 @@ class ToolsCalcFinalWordDoneView(View):
self.controller.storage.convert_pending_mnemonic_to_pending_seed()
if button_data[selected_menu_num] == LOAD:
if button_data[selected_menu_num] == self.LOAD:
return Destination(SeedFinalizeView)
elif button_data[selected_menu_num] == DISCARD:
elif button_data[selected_menu_num] == self.DISCARD:
return Destination(SeedDiscardView)
"""****************************************************************************
Address Explorer Views
****************************************************************************"""
class ToolsAddressExplorerSelectSourceView(View):
SCAN_SEED = ("Scan a seed", SeedSignerIconConstants.QRCODE)
SCAN_DESCRIPTOR = ("Scan wallet descriptor", SeedSignerIconConstants.QRCODE)
TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD)
TYPE_ELECTRUM = ("Enter Electrum seed", FontAwesomeIconConstants.KEYBOARD)
SCAN_SEED = (_("Scan a seed"), SeedSignerIconConstants.QRCODE)
SCAN_DESCRIPTOR = (_("Scan wallet descriptor"), SeedSignerIconConstants.QRCODE)
TYPE_12WORD = (_("Enter 12-word seed"), FontAwesomeIconConstants.KEYBOARD)
TYPE_24WORD = (_("Enter 24-word seed"), FontAwesomeIconConstants.KEYBOARD)
TYPE_ELECTRUM = (_("Enter Electrum seed"), FontAwesomeIconConstants.KEYBOARD)
def run(self):
seeds = self.controller.storage.seeds
self.SCAN_SEED = (_("Scan a seed"), SeedSignerIconConstants.QRCODE)
self.SCAN_DESCRIPTOR = (_("Scan wallet descriptor"), SeedSignerIconConstants.QRCODE)
self.TYPE_12WORD = (_("Enter 12-word seed"), FontAwesomeIconConstants.KEYBOARD)
self.TYPE_24WORD = (_("Enter 24-word seed"), FontAwesomeIconConstants.KEYBOARD)
button_data = []
for seed in seeds:
button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
@@ -529,8 +518,11 @@ class ToolsAddressExplorerSelectSourceView(View):
class ToolsAddressExplorerAddressTypeView(View):
RECEIVE = "Receive Addresses"
CHANGE = "Change Addresses"
# TRANSLATOR_NOTE: label for addresses where others send us incoming payments
RECEIVE = _("Receive Addresses")
# TRANSLATOR_NOTE: label for addresses that collect the change from our own outgoing payments
CHANGE = _("Change Addresses")
def __init__(self, seed_num: int = None, script_type: str = None, custom_derivation: str = None):
@@ -584,11 +576,6 @@ class ToolsAddressExplorerAddressTypeView(View):
def run(self):
data = self.controller.address_explorer_data
# TRANSLATOR_NOTE: label for addresses where others send us incoming payments
self.RECEIVE = _("Receive Addresses")
# TRANSLATOR_NOTE: label for addresses that collect the change from our own outgoing payments
self.CHANGE = _("Change Addresses")
wallet_descriptor_display_name = None
if "wallet_descriptor" in data:
wallet_descriptor_display_name = data["wallet_descriptor"].brief_policy.replace(" (sorted)", "")
+12 -18
View File
@@ -182,17 +182,14 @@ class Destination:
#
#########################################################################################
class MainMenuView(View):
SCAN = ("Scan", SeedSignerIconConstants.SCAN)
SEEDS = ("Seeds", SeedSignerIconConstants.SEEDS)
TOOLS = ("Tools", SeedSignerIconConstants.TOOLS)
SETTINGS = ("Settings", SeedSignerIconConstants.SETTINGS)
SCAN = (_("Scan"), SeedSignerIconConstants.SCAN)
SEEDS = (_("Seeds"), SeedSignerIconConstants.SEEDS)
TOOLS = (_("Tools"), SeedSignerIconConstants.TOOLS)
SETTINGS = (_("Settings"), SeedSignerIconConstants.SETTINGS)
def run(self):
self.SCAN = (_("Scan"), SeedSignerIconConstants.SCAN)
self.SEEDS = (_("Seeds"), SeedSignerIconConstants.SEEDS)
self.TOOLS = (_("Tools"), SeedSignerIconConstants.TOOLS)
self.SETTINGS = (_("Settings"), SeedSignerIconConstants.SETTINGS)
from seedsigner.gui.screens.screen import MainMenuScreen
button_data = [self.SCAN, self.SEEDS, self.TOOLS, self.SETTINGS]
selected_menu_num = self.run_screen(
MainMenuScreen,
@@ -222,12 +219,10 @@ class MainMenuView(View):
class PowerOptionsView(View):
RESET = ("Restart", SeedSignerIconConstants.RESTART)
POWER_OFF = ("Power Off", SeedSignerIconConstants.POWER)
RESET = (_("Restart"), SeedSignerIconConstants.RESTART)
POWER_OFF = (_("Power Off"), SeedSignerIconConstants.POWER)
def run(self):
self.RESET = (_("Restart"), SeedSignerIconConstants.RESTART)
self.POWER_OFF = (_("Power Off"), SeedSignerIconConstants.POWER)
button_data = [self.RESET, self.POWER_OFF]
selected_menu_num = self.run_screen(
LargeButtonScreen,
@@ -321,7 +316,7 @@ class NotYetImplementedView(View):
@dataclass
class ErrorView(View):
title: str = "Error"
title: str = _("Error")
show_back_button: bool = True
status_headline: str = None
text: str = None
@@ -330,7 +325,6 @@ class ErrorView(View):
def run(self):
self.title = _("Error")
self.run_screen(
WarningScreen,
title=self.title,
@@ -346,16 +340,14 @@ class ErrorView(View):
@dataclass
class NetworkMismatchErrorView(ErrorView):
title: str = "Network Mismatch"
title: str = _("Network Mismatch")
show_back_button: bool = False
button_text: str = "Change Setting"
button_text: str = _("Change Setting")
next_destination: Destination = None
def __post_init__(self):
super().__post_init__()
self.title = _("Network Mismatch")
self.button_text = _("Change Setting")
if not self.text:
# TRANSLATOR_NOTE: Inserts mainnet/testnet/regtest
self.text = _("Current network setting ({}) doesn't match current action.").format(
@@ -377,6 +369,8 @@ class UnhandledExceptionView(View):
self.run_screen(
DireWarningScreen,
title=_("System Error"),
status_headline=self.error[0],
text=self.error[1] + "\n" + self.error[2],
allow_text_overflow=True, # Fit what we can, let the rest go off the edges
)
+2 -2
View File
@@ -342,8 +342,8 @@ def test_generate_screenshots(target_locale):
time.sleep(0.1) #jdlcdl
# Parse the main `babel/messages.pot` for overall stats
messages_source_path = os.path.join(pathlib.Path(__file__).parent.resolve().parent.resolve().parent.resolve(), "babel", "messages.pot")
# Parse the main `l10n/messages.pot` for overall stats
messages_source_path = os.path.join(pathlib.Path(__file__).parent.resolve().parent.resolve().parent.resolve(), "l10n", "messages.pot")
with open(messages_source_path, 'r') as messages_source_file:
num_source_messages = messages_source_file.read().count("msgid \"") - 1