4 Commits

Author SHA1 Message Date
346aeee62b Merge remote-tracking branch 'origin/main' 2026-10-08 19:01:23 -04:00
3b2eed37cc i18n: fix Babel format-string error on the heirs wizard text
The literal '%' in "100% of wallet balance" was detected as a Python
format specifier (%o in English vs %d in the translations), so
pybabel compile failed and the catalogs could not be built. Reword to
put '100%' at the end of the sentence and refresh es_ES/it_IT.
2026-10-08 19:00:51 -04:00
0c60462163 Merge pull request 'i18n review fixes (#63)' (#10) from fix/i18n-review into main
Reviewed-on: #10
2026-10-08 20:43:21 +00:00
a81443ff67 i18n review fixes (#63)
Truman's i18n review: release gate runs the i18n, CLI and import tests and checks Babel first; ruff known-first-party = bal; no translated text stored in the wallet (executor push records are copies, status history always saved as 'New', old 'Nuovo'/'Nuevo' mapped back on load).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01HxDDvLym1NEbFNYMc1rr7S
2026-10-07 15:03:25 +02:00
13 changed files with 867 additions and 601 deletions

View File

@@ -117,7 +117,10 @@ python3 build_zip.py # -> bal-electrum-plugin.zip (deterministic, prints sha25
``` ```
- `build_zip.py` needs **Babel** (`pip install babel`): it compiles the - `build_zip.py` needs **Babel** (`pip install babel`): it compiles the
translation catalogs into the zip and stops if Babel is missing. translation catalogs into the zip and stops if Babel is missing. The i18n
tests need it too. Install it in the **runtime** env used for releases
(`source "$BAL_HOME/electrum/env/bin/activate" && pip install babel`);
`make-release.sh` checks it before running anything.
- `make-release.sh` requires gpg and Gitea credentials (`~/.git-credentials` - `make-release.sh` requires gpg and Gitea credentials (`~/.git-credentials`
or `GITEA_USER`/`GITEA_TOKEN`). It bumps `bal/manifest.json` — bump the or `GITEA_USER`/`GITEA_TOKEN`). It bumps `bal/manifest.json` — bump the
version there, never invent a new source of truth. version there, never invent a new source of truth.

View File

@@ -3549,3 +3549,66 @@ delivery; when it is not, the plugin raises an explicit error (see below).
catalogs refreshed (removed format strings marked obsolete). catalogs refreshed (removed format strings marked obsolete).
**Outcome:** DONE. **Outcome:** DONE.
---
## 63. i18n review fixes (Truman's review of the translation PR)
**Date:** 2026-10-07
**What changed:**
- `make-release.sh`
- Step 3 now also runs `tests/test_i18n.py`, `tests/test_translations.py`,
`tests/test_cli_*.py` and `tests/test_import_will_details.py` (the i18n
tests were never run by the release gate; the CLI and will-import tests
were not either).
- Checks up front that Babel is importable by `python3` and stops with
"pip install babel" otherwise: the i18n tests (step 3) and `build_zip.py`
(step 5) both need it, so a release no longer stops half-way.
- `AGENTS.md`: Babel must be installed in the runtime env used for releases.
- `pyproject.toml`: `[tool.ruff.lint.isort] known-first-party = ["bal"]`.
WHY: the reported I001 in `tests/test_translations.py` is not reproducible
from the repository root (ruff 0.15.11 and 0.6.9: clean), but it appears as
soon as ruff classifies `bal` as third-party (then 20 files report I001).
Removing the blank line would break the check where `bal` is first-party;
declaring it makes the result the same on every machine.
- `bal/core/willexecutors.py` - `get_willexecutor_transactions` returns COPIES
of the executor records. It used to write `txs`, `txsids` and the
translated `broadcast_status` into the will item's own `we` dict, which is
saved in the wallet (stored data depended on the UI language).
- `bal/core/will.py` - `WillItem` drops those transient fields when loading a
will saved by an older version, and maps a translated leading "New"
("Nuovo", "Nuevo" - saved by i18n test builds) back to "New".
- `bal/gui/qt/window.py`, `bal/cli/controller.py`: the status history of a new
will item is saved as the literal "New" (was `_("New")`).
- `tests/test_core_status_language_neutral.py` (new, 7 tests; 5 fail on
`main`): English "New" stored, translated "New" mapped back, the mapping
covers every catalog's translation of "New", the push helper does not
modify the will item, old transient fields dropped on load.
**Scope:** this entry contains ONLY the i18n review fixes. The other fixes
prepared in the same session (invalidation fee, Check `KeyError`, cascade,
deselected executor) are not part of it and will be proposed separately.
**Verification (on `main` 827a1d1):**
- New test file `tests/test_core_status_language_neutral.py`: 5 of 7 fail on
`main`, 7 of 7 pass with this change.
- Extended release test list (as above) plus the new test file: 446 passed,
1 failed (`main`: 439 passed, same failure). The failure is pre-existing:
`test_group_e_mock_karen7::test_e5` needs `tests/karen7`, a wallet file that
is not in the repository.
- Whole `tests/` directory: 609 passed, 26 failed, 3 collection errors
(`main`: 602 passed, and the SAME 26 failures and 3 errors, test by test).
All pre-existing and unrelated: 9 need `tests/karen7`; 21 in
`test_auto_rebuild_on_new_tx.py`, `test_cli_autorebuild.py` and
`test_cli_controller_offline.py` fail only when the whole directory runs
together (they pass when each file runs alone); 3 audio tests need an audio
device. `test_gui_export_dialogs.py::test_audio_import_receive_wiring` hangs
forever, also on `main`, so it was deselected.
- `ruff check bal/ tests/`: the same 3 pre-existing I001 as on `main`
(`dialogs.py`, `test_core_animated_qr.py`, `test_core_qr_transfer.py`),
none new.
**Outcome:** DONE (pending Truman's review).

View File

@@ -385,7 +385,9 @@ class BalController:
tx["heirsvalue"] = txs[txid].heirsvalue tx["heirsvalue"] = txs[txid].heirsvalue
tx["description"] = txs[txid].description tx["description"] = txs[txid].description
tx["willexecutor"] = copy_structure(txs[txid].willexecutor) tx["willexecutor"] = copy_structure(txs[txid].willexecutor)
tx["status"] = _("New") # Stored history: always English (translated only for display by
# format_status_history); never _() here.
tx["status"] = "New"
tx["baltx_fees"] = txs[txid].tx_fees tx["baltx_fees"] = txs[txid].tx_fees
tx["time"] = creation_time tx["time"] = creation_time
tx["heirs"] = copy_structure(txs[txid].heirs) tx["heirs"] = copy_structure(txs[txid].heirs)

View File

@@ -1364,6 +1364,37 @@ class WillItem(Logger):
# format_status_history() still translates them. # format_status_history() still translates them.
LEGACY_STATUS_LABELS = (N_("New"),) LEGACY_STATUS_LABELS = (N_("New"),)
# Translations of "New" that test builds of the i18n work saved INTO the
# status history (window.py / the CLI used ``_("New")`` when building).
# They are mapped back to "New" on load so the history is language
# neutral again. Keep in sync with the "New" msgstr of every catalog in
# bal/locale (tests/test_core_status_language_neutral.py checks it).
TRANSLATED_NEW_LABELS = ("Nuovo", "Nuevo")
# Fields that Willexecutors.get_willexecutor_transactions /
# push_transactions_to_willexecutor used to write into the item's own
# executor record (older versions); they are transient push data.
TRANSIENT_PUSH_FIELDS = ("txs", "txsids", "broadcast_status")
@staticmethod
def _untranslate_new_status(status):
"""Map a translated leading "New" of a saved history back to "New"."""
head, sep, rest = status.partition(".")
if head in WillItem.TRANSLATED_NEW_LABELS:
return "New" + sep + rest
return status
@staticmethod
def _without_transient_push_fields(we):
"""Return ``we`` without the transient push fields (a copy if needed)."""
if not isinstance(we, dict) or not any(
k in we for k in WillItem.TRANSIENT_PUSH_FIELDS
):
return we
return {
k: v for k, v in we.items() if k not in WillItem.TRANSIENT_PUSH_FIELDS
}
def set_status(self, status, value=True): def set_status(self, status, value=True):
"""Set a status flag and apply the related side effects. """Set a status flag and apply the related side effects.
@@ -1445,8 +1476,10 @@ class WillItem(Logger):
w = data w = data
self.tx = Will.get_tx_from_any(w["tx"]) self.tx = Will.get_tx_from_any(w["tx"])
self.heirs = w.get("heirs", None) self.heirs = w.get("heirs", None)
self.we = w.get("willexecutor", None) self.we = WillItem._without_transient_push_fields(
self.status = w.get("status") or "" w.get("willexecutor", None)
)
self.status = WillItem._untranslate_new_status(w.get("status") or "")
self.description = w.get("description", None) self.description = w.get("description", None)
self.time = w.get("time", None) self.time = w.get("time", None)
self.change = w.get("change", None) self.change = w.get("change", None)

View File

@@ -231,6 +231,13 @@ class Willexecutors:
@staticmethod @staticmethod
def get_willexecutor_transactions(will, force=False): def get_willexecutor_transactions(will, force=False):
"""Group the valid, signed, not-yet-pushed will txs by will-executor.
Returns ``{url: executor_record}`` where each record gets ``txs`` (the
raw txs, one per line) and ``txsids``.
The returned records are COPIES: the will items are never modified.
"""
willexecutors = {} willexecutors = {}
for wid, willitem in will.items(): for wid, willitem in will.items():
if willitem.get_status("VALID"): if willitem.get_status("VALID"):
@@ -240,10 +247,18 @@ class Willexecutors:
url = willexecutor["url"] url = willexecutor["url"]
if willexecutor and Willexecutors.is_selected(willexecutor): if willexecutor and Willexecutors.is_selected(willexecutor):
if url not in willexecutors: if url not in willexecutors:
willexecutor["txs"] = "" # Work on a COPY of the record: the item's
willexecutor["txsids"] = [] # own ``we`` dict is saved in the wallet, so
willexecutor["broadcast_status"] = _("Waiting...") # the transient push fields below (raw txs,
willexecutors[url] = willexecutor # txids and the - translated - broadcast
# status shown in the push dialog) must
# never end up in it (stored data must not
# depend on the UI language, see bal/i18n.py).
record = dict(willexecutor)
record["txs"] = ""
record["txsids"] = []
record["broadcast_status"] = _("Waiting...")
willexecutors[url] = record
willexecutors[url]["txs"] += str(willitem.tx) + "\n" willexecutors[url]["txs"] += str(willitem.tx) + "\n"
willexecutors[url]["txsids"].append(wid) willexecutors[url]["txsids"].append(wid)

View File

@@ -358,7 +358,8 @@ class BalWizardWidget(QWidget):
class BalWizardHeirsWidget(BalWizardWidget): class BalWizardHeirsWidget(BalWizardWidget):
title = N_("Bitcoin After Life Heirs") title = N_("Bitcoin After Life Heirs")
message = N_( message = N_(
"Please add your heirs\n remember that 100% of wallet balance will be spent" "Please add your heirs\n"
" remember that the wallet balance will be spent at 100%"
) )
def get_content(self): def get_content(self):

View File

@@ -551,7 +551,9 @@ class BalWindow:
tx["heirsvalue"] = txs[txid].heirsvalue tx["heirsvalue"] = txs[txid].heirsvalue
tx["description"] = txs[txid].description tx["description"] = txs[txid].description
tx["willexecutor"] = copy_structure(txs[txid].willexecutor) tx["willexecutor"] = copy_structure(txs[txid].willexecutor)
tx["status"] = _("New") # Stored history: always English (translated only for display by
# format_status_history); never _() here.
tx["status"] = "New"
tx["baltx_fees"] = txs[txid].tx_fees tx["baltx_fees"] = txs[txid].tx_fees
tx["time"] = creation_time tx["time"] = creation_time
tx["heirs"] = copy_structure(txs[txid].heirs) tx["heirs"] = copy_structure(txs[txid].heirs)

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-10-03 12:24-0400\n" "POT-Creation-Date: 2026-10-07 14:25-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -60,7 +60,7 @@ msgstr ""
msgid "Error" msgid "Error"
msgstr "" msgstr ""
#: bal/core/will.py:1348 bal/gui/qt/dialogs.py:824 #: bal/core/will.py:1348 bal/gui/qt/dialogs.py:825
msgid "Expired" msgid "Expired"
msgstr "" msgstr ""
@@ -104,7 +104,7 @@ msgstr ""
msgid "Updated" msgid "Updated"
msgstr "" msgstr ""
#: bal/core/will.py:1359 bal/gui/qt/dialogs.py:2632 #: bal/core/will.py:1359 bal/gui/qt/dialogs.py:2633
msgid "Valid" msgid "Valid"
msgstr "" msgstr ""
@@ -195,15 +195,15 @@ msgstr ""
msgid "Bal Wizard Setup" msgid "Bal Wizard Setup"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:325 bal/gui/qt/dialogs.py:2760 #: bal/gui/qt/dialogs.py:325 bal/gui/qt/dialogs.py:2762
msgid "Previous" msgid "Previous"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:329 bal/gui/qt/dialogs.py:2763 #: bal/gui/qt/dialogs.py:329 bal/gui/qt/dialogs.py:2765
msgid "Next" msgid "Next"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:333 bal/gui/qt/dialogs.py:4019 #: bal/gui/qt/dialogs.py:333 bal/gui/qt/dialogs.py:4048
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@@ -212,194 +212,193 @@ msgid "Bitcoin After Life Heirs"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:360 #: bal/gui/qt/dialogs.py:360
#, python-format
msgid "" msgid ""
"Please add your heirs\n" "Please add your heirs\n"
" remember that 100% of wallet balance will be spent" " remember that the wallet balance will be spent at 100%"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:369 bal/gui/qt/lists.py:1229 #: bal/gui/qt/dialogs.py:370 bal/gui/qt/lists.py:1229
msgid "Add" msgid "Add"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:371 bal/gui/qt/dialogs.py:2593 bal/gui/qt/lists.py:311 #: bal/gui/qt/dialogs.py:372 bal/gui/qt/dialogs.py:2594 bal/gui/qt/lists.py:311
#: bal/gui/qt/lists.py:673 bal/gui/qt/lists.py:1237 #: bal/gui/qt/lists.py:673 bal/gui/qt/lists.py:1237
msgid "Import" msgid "Import"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:373 bal/gui/qt/dialogs.py:2429 #: bal/gui/qt/dialogs.py:374 bal/gui/qt/dialogs.py:2430
#: bal/gui/qt/dialogs.py:2597 bal/gui/qt/lists.py:312 bal/gui/qt/lists.py:672 #: bal/gui/qt/dialogs.py:2598 bal/gui/qt/lists.py:312 bal/gui/qt/lists.py:672
#: bal/gui/qt/lists.py:1249 #: bal/gui/qt/lists.py:1249
msgid "Export" msgid "Export"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:398 bal/gui/qt/dialogs.py:506 #: bal/gui/qt/dialogs.py:399 bal/gui/qt/dialogs.py:507
msgid "Bitcoin After Life Will-Executors" msgid "Bitcoin After Life Will-Executors"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:399 #: bal/gui/qt/dialogs.py:400
msgid "Choose willexecutors download method" msgid "Choose willexecutors download method"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:406 #: bal/gui/qt/dialogs.py:407
msgid "Automatically download and select willexecutors" msgid "Automatically download and select willexecutors"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:407 #: bal/gui/qt/dialogs.py:408
msgid "Only download willexecutors list" msgid "Only download willexecutors list"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:408 #: bal/gui/qt/dialogs.py:409
msgid "Import willexecutor list from file" msgid "Import willexecutor list from file"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:409 #: bal/gui/qt/dialogs.py:410
msgid "Manual" msgid "Manual"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:438 bal/gui/qt/lists.py:1559 #: bal/gui/qt/dialogs.py:439 bal/gui/qt/lists.py:1559
msgid "willexecutors" msgid "willexecutors"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:507 #: bal/gui/qt/dialogs.py:508
msgid "Configure and select your willexecutors" msgid "Configure and select your willexecutors"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:526 #: bal/gui/qt/dialogs.py:527
msgid "Bitcoin After Life Will Settings" msgid "Bitcoin After Life Will Settings"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:561 bal/gui/qt/dialogs.py:614 #: bal/gui/qt/dialogs.py:562 bal/gui/qt/dialogs.py:615
msgid "Please wait" msgid "Please wait"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:644 #: bal/gui/qt/dialogs.py:645
msgid "Building Will" msgid "Building Will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:649 #: bal/gui/qt/dialogs.py:650
msgid "Building Will:" msgid "Building Will:"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:730 bal/gui/qt/dialogs.py:755 #: bal/gui/qt/dialogs.py:731 bal/gui/qt/dialogs.py:756
#: bal/gui/qt/dialogs.py:765 bal/gui/qt/dialogs.py:788 #: bal/gui/qt/dialogs.py:766 bal/gui/qt/dialogs.py:789
msgid "Checking variables" msgid "Checking variables"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:756 #: bal/gui/qt/dialogs.py:757
msgid "Check Alive Threshold Passed: you have to Invalidate your old Will" msgid "Check Alive Threshold Passed: you have to Invalidate your old Will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:765 bal/gui/qt/dialogs.py:847 #: bal/gui/qt/dialogs.py:766 bal/gui/qt/dialogs.py:848
msgid "No Heirs" msgid "No Heirs"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:788 bal/gui/qt/dialogs.py:1335 #: bal/gui/qt/dialogs.py:789 bal/gui/qt/dialogs.py:1336
#: bal/gui/qt/dialogs.py:2281 bal/gui/qt/window.py:2315 #: bal/gui/qt/dialogs.py:2282 bal/gui/qt/window.py:2315
msgid "Ok" msgid "Ok"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:792 #: bal/gui/qt/dialogs.py:793
msgid "" msgid ""
"In the inheritance process, the entire wallet will always be fully emptied. \n" "In the inheritance process, the entire wallet will always be fully emptied. \n"
"Your settings require an adjustment of the amounts" "Your settings require an adjustment of the amounts"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:801 bal/gui/qt/window.py:844 #: bal/gui/qt/dialogs.py:802 bal/gui/qt/window.py:844
msgid "Will-executor fee too high: {}" msgid "Will-executor fee too high: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:835 #: bal/gui/qt/dialogs.py:836
msgid "Postponed: invalidating old will" msgid "Postponed: invalidating old will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:870 bal/gui/qt/dialogs.py:879 #: bal/gui/qt/dialogs.py:871 bal/gui/qt/dialogs.py:880
#: bal/gui/qt/dialogs.py:2241 #: bal/gui/qt/dialogs.py:2242
msgid "Checking your will" msgid "Checking your will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:872 #: bal/gui/qt/dialogs.py:873
msgid "An inheritance of this wallet is already executed (on blockchain)" msgid "An inheritance of this wallet is already executed (on blockchain)"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:881 #: bal/gui/qt/dialogs.py:882
msgid "Inheritance in mempool (waiting confirmation)" msgid "Inheritance in mempool (waiting confirmation)"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:914 #: bal/gui/qt/dialogs.py:915
msgid "Will-Executor excluded" msgid "Will-Executor excluded"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:914 bal/gui/qt/dialogs.py:1008 #: bal/gui/qt/dialogs.py:915 bal/gui/qt/dialogs.py:1009
#: bal/gui/qt/dialogs.py:2222 bal/gui/qt/dialogs.py:2233 #: bal/gui/qt/dialogs.py:2223 bal/gui/qt/dialogs.py:2234
msgid "Skipped" msgid "Skipped"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:920 bal/gui/qt/dialogs.py:1705 bal/gui/qt/lists.py:345 #: bal/gui/qt/dialogs.py:921 bal/gui/qt/dialogs.py:1706 bal/gui/qt/lists.py:345
#: bal/gui/qt/theme.py:116 #: bal/gui/qt/theme.py:116
msgid "Will-Executor" msgid "Will-Executor"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:921 #: bal/gui/qt/dialogs.py:922
msgid "Not present - select one or enable backup mode" msgid "Not present - select one or enable backup mode"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:982 #: bal/gui/qt/dialogs.py:983
msgid "All heirs' shares are below the dust limit: the inheritance cannot be created. Increase the amounts or reduce the number of heirs." msgid "All heirs' shares are below the dust limit: the inheritance cannot be created. Increase the amounts or reduce the number of heirs."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:998 #: bal/gui/qt/dialogs.py:999
msgid "Wallet balance is too low: {} satoshi available, but the miner and will-executor fees need {} satoshi (the minimum usable amount is {} satoshi). Add funds, or select fewer will-executors." msgid "Wallet balance is too low: {} satoshi available, but the miner and will-executor fees need {} satoshi (the minimum usable amount is {} satoshi). Add funds, or select fewer will-executors."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1042 #: bal/gui/qt/dialogs.py:1043
msgid "Heir {}" msgid "Heir {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1044 #: bal/gui/qt/dialogs.py:1045
msgid "{} is DUST - excluded (amount below dust limit)" msgid "{} is DUST - excluded (amount below dust limit)"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1100 #: bal/gui/qt/dialogs.py:1101
msgid "Heir" msgid "Heir"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1217 bal/gui/qt/dialogs.py:2270 #: bal/gui/qt/dialogs.py:1218 bal/gui/qt/dialogs.py:2271
msgid "Please wait {}secs" msgid "Please wait {}secs"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1224 bal/gui/qt/dialogs.py:1276 #: bal/gui/qt/dialogs.py:1225 bal/gui/qt/dialogs.py:1277
#: bal/gui/qt/dialogs.py:1314 #: bal/gui/qt/dialogs.py:1315
msgid "Broadcasting" msgid "Broadcasting"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1335 bal/gui/qt/window.py:2317 #: bal/gui/qt/dialogs.py:1336 bal/gui/qt/window.py:2317
msgid "Ko" msgid "Ko"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1346 #: bal/gui/qt/dialogs.py:1347
msgid "Timeout - no answer" msgid "Timeout - no answer"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1388 #: bal/gui/qt/dialogs.py:1389
msgid "checking {} - {}" msgid "checking {} - {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1391 bal/gui/qt/dialogs.py:2239 #: bal/gui/qt/dialogs.py:1392 bal/gui/qt/dialogs.py:2240
#: bal/gui/qt/window.py:1691 #: bal/gui/qt/window.py:1691
msgid "Waiting" msgid "Waiting"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1405 #: bal/gui/qt/dialogs.py:1406
msgid "checked {} - {}" msgid "checked {} - {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1455 bal/gui/qt/dialogs.py:2025 #: bal/gui/qt/dialogs.py:1456 bal/gui/qt/dialogs.py:2026
#: bal/gui/qt/dialogs.py:2036 #: bal/gui/qt/dialogs.py:2037
msgid "Error: {}" msgid "Error: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1516 #: bal/gui/qt/dialogs.py:1517
msgid "" msgid ""
"Your will has expired and must be invalidated before it can be rebuilt.\n" "Your will has expired and must be invalidated before it can be rebuilt.\n"
"A transaction window will now open:\n" "A transaction window will now open:\n"
@@ -407,159 +406,159 @@ msgid ""
"After the invalidation is confirmed, press the Check button to finish the will." "After the invalidation is confirmed, press the Check button to finish the will."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1545 bal/gui/qt/window.py:849 #: bal/gui/qt/dialogs.py:1546 bal/gui/qt/window.py:849
msgid "Check Alive must be after today and before the delivery date, please update it" msgid "Check Alive must be after today and before the delivery date, please update it"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1568 #: bal/gui/qt/dialogs.py:1569
msgid "" msgid ""
"Your old will has been invalidated and the transaction was broadcast.\n" "Your old will has been invalidated and the transaction was broadcast.\n"
"Electrum may need a little time to register it.\n" "Electrum may need a little time to register it.\n"
"Please wait until the invalidation transaction is confirmed, then press the Check button again to finish updating your will." "Please wait until the invalidation transaction is confirmed, then press the Check button again to finish updating your will."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1584 #: bal/gui/qt/dialogs.py:1585
msgid "Invalidate your old will" msgid "Invalidate your old will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1598 bal/gui/qt/dialogs.py:1643 #: bal/gui/qt/dialogs.py:1599 bal/gui/qt/dialogs.py:1644
msgid "Aborted" msgid "Aborted"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1617 #: bal/gui/qt/dialogs.py:1618
msgid "Auto-sign disabled — sign manually" msgid "Auto-sign disabled — sign manually"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1630 #: bal/gui/qt/dialogs.py:1631
msgid "" msgid ""
"The delivery date was automatically moved one day earlier so the updated will can correctly replace the previous one.\n" "The delivery date was automatically moved one day earlier so the updated will can correctly replace the previous one.\n"
"Please sign (and broadcast) to confirm the change." "Please sign (and broadcast) to confirm the change."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1640 #: bal/gui/qt/dialogs.py:1641
msgid "Sign your will" msgid "Sign your will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1645 bal/gui/qt/dialogs.py:1928 #: bal/gui/qt/dialogs.py:1646 bal/gui/qt/dialogs.py:1929
msgid "Nothing to do" msgid "Nothing to do"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1664 #: bal/gui/qt/dialogs.py:1665
msgid "Finished" msgid "Finished"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1681 #: bal/gui/qt/dialogs.py:1682
msgid "Calendar" msgid "Calendar"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1683 bal/gui/qt/dialogs.py:1713 #: bal/gui/qt/dialogs.py:1684 bal/gui/qt/dialogs.py:1714
#: bal/gui/qt/dialogs.py:1780 bal/gui/qt/dialogs.py:2028 #: bal/gui/qt/dialogs.py:1781 bal/gui/qt/dialogs.py:2029
#: bal/gui/qt/dialogs.py:2968 bal/gui/qt/dialogs.py:3207 #: bal/gui/qt/dialogs.py:2976 bal/gui/qt/dialogs.py:3231
#: bal/gui/qt/dialogs.py:4038 #: bal/gui/qt/dialogs.py:4067
msgid "Close" msgid "Close"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1709 bal/gui/qt/dialogs.py:1776 #: bal/gui/qt/dialogs.py:1710 bal/gui/qt/dialogs.py:1777
msgid "🔮 Wizard" msgid "🔮 Wizard"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1772 bal/gui/qt/dialogs.py:2580 #: bal/gui/qt/dialogs.py:1773 bal/gui/qt/dialogs.py:2581
msgid "Heirs" msgid "Heirs"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1881 bal/gui/qt/widgets.py:1150 #: bal/gui/qt/dialogs.py:1882 bal/gui/qt/widgets.py:1150
msgid "(reminder {idx}/{total})" msgid "(reminder {idx}/{total})"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1945 #: bal/gui/qt/dialogs.py:1946
msgid "All done" msgid "All done"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:1996 #: bal/gui/qt/dialogs.py:1997
msgid "Next steps (manual): press 'Sign' to sign your will, then 'Broadcast' to send it to the will-executors." msgid "Next steps (manual): press 'Sign' to sign your will, then 'Broadcast' to send it to the will-executors."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2001 #: bal/gui/qt/dialogs.py:2002
msgid "Next step (manual): press 'Sign' to sign your will." msgid "Next step (manual): press 'Sign' to sign your will."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2005 #: bal/gui/qt/dialogs.py:2006
msgid "Next step (manual): press 'Broadcast' to send your will to the will-executors." msgid "Next step (manual): press 'Broadcast' to send your will to the will-executors."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2100 #: bal/gui/qt/dialogs.py:2101
msgid "Miner fee rate changed (the will was built with {} sat/byte, now it is {}): a new will must be prepared." msgid "Miner fee rate changed (the will was built with {} sat/byte, now it is {}): a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2105 #: bal/gui/qt/dialogs.py:2106
msgid "The miner fee rate changed: a new will must be prepared." msgid "The miner fee rate changed: a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2109 #: bal/gui/qt/dialogs.py:2110
msgid "Will-executor \"{}\" is not covered by the current will: a new will must be prepared." msgid "Will-executor \"{}\" is not covered by the current will: a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2114 #: bal/gui/qt/dialogs.py:2115
msgid "Backup mode is enabled but the will has no backup transaction: a new will must be prepared." msgid "Backup mode is enabled but the will has no backup transaction: a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2123 #: bal/gui/qt/dialogs.py:2124
msgid "Heir \"{}\" is not covered by the current will (it was added or removed, or its delivery date changed): a new will must be prepared." msgid "Heir \"{}\" is not covered by the current will (it was added or removed, or its delivery date changed): a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2132 #: bal/gui/qt/dialogs.py:2133
msgid "The heirs changed: a new will must be prepared." msgid "The heirs changed: a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2134 #: bal/gui/qt/dialogs.py:2135
msgid "A will-executor changed: a new will must be prepared." msgid "A will-executor changed: a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2143 #: bal/gui/qt/dialogs.py:2144
msgid "The wallet contains funds that the current will does not cover yet: a new will must be prepared." msgid "The wallet contains funds that the current will does not cover yet: a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2147 #: bal/gui/qt/dialogs.py:2148
msgid "The will contains no valid transaction: a new will must be prepared." msgid "The will contains no valid transaction: a new will must be prepared."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2153 #: bal/gui/qt/dialogs.py:2154
msgid "The will is no longer coherent and must be rebuilt; the exact reason could not be determined." msgid "The will is no longer coherent and must be rebuilt; the exact reason could not be determined."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2183 #: bal/gui/qt/dialogs.py:2184
msgid "No heirs: add at least one heir before building the will." msgid "No heirs: add at least one heir before building the will."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2186 #: bal/gui/qt/dialogs.py:2187
msgid "The wallet has no spendable funds, so no inheritance transaction can be created." msgid "The wallet has no spendable funds, so no inheritance transaction can be created."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2190 #: bal/gui/qt/dialogs.py:2191
msgid "No usable will-executor: none of the servers in the list is both selected and valid. Open the will-executor settings, select at least one server and check that it is reachable." msgid "No usable will-executor: none of the servers in the list is both selected and valid. Open the will-executor settings, select at least one server and check that it is reachable."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2195 #: bal/gui/qt/dialogs.py:2196
msgid "No delivery date left to build: every heir's date is already covered by the existing will. Choose a later delivery date, or change an heir's date." msgid "No delivery date left to build: every heir's date is already covered by the existing will. Choose a later delivery date, or change an heir's date."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2200 #: bal/gui/qt/dialogs.py:2201
msgid "The amount to send must cover the miner fees plus this will-executor's fee, and the wallet balance is not enough: select cheaper will-executors, or add funds to the wallet." msgid "The amount to send must cover the miner fees plus this will-executor's fee, and the wallet balance is not enough: select cheaper will-executors, or add funds to the wallet."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2205 #: bal/gui/qt/dialogs.py:2206
msgid "A will-executor asks for more than the maximum fee you allowed: raise the maximum fee in the settings, or select a cheaper will-executor." msgid "A will-executor asks for more than the maximum fee you allowed: raise the maximum fee in the settings, or select a cheaper will-executor."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2210 #: bal/gui/qt/dialogs.py:2211
msgid "The inheritance transactions could not be assembled from the available funds (the balance may not cover the miner fees)." msgid "The inheritance transactions could not be assembled from the available funds (the balance may not cover the miner fees)."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2214 #: bal/gui/qt/dialogs.py:2215
msgid "An unexpected error stopped the transactions being prepared for a will-executor, so it was skipped. Try again, or select a different will-executor." msgid "An unexpected error stopped the transactions being prepared for a will-executor, so it was skipped. Try again, or select a different will-executor."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2225 #: bal/gui/qt/dialogs.py:2226
msgid "" msgid ""
"Could not build the will, and the exact cause could not be determined. Please check that:\n" "Could not build the will, and the exact cause could not be determined. Please check that:\n"
"- the wallet balance covers the miner and will-executor fees,\n" "- the wallet balance covers the miner and will-executor fees,\n"
@@ -568,403 +567,403 @@ msgid ""
"- at least one will-executor is selected and reachable." "- at least one will-executor is selected and reachable."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2246 #: bal/gui/qt/dialogs.py:2247
msgid "Invalidating old will" msgid "Invalidating old will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2252 #: bal/gui/qt/dialogs.py:2253
msgid "Building your will" msgid "Building your will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2258 #: bal/gui/qt/dialogs.py:2259
msgid "Signing your will" msgid "Signing your will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2264 #: bal/gui/qt/dialogs.py:2265
msgid "Broadcasting your will to executors" msgid "Broadcasting your will to executors"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2312 #: bal/gui/qt/dialogs.py:2313
msgid "Wait" msgid "Wait"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2415 #: bal/gui/qt/dialogs.py:2416
msgid "Will Details" msgid "Will Details"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2421 bal/gui/qt/dialogs.py:4013 #: bal/gui/qt/dialogs.py:2422 bal/gui/qt/dialogs.py:4042
#: bal/gui/qt/lists.py:668 #: bal/gui/qt/lists.py:668
msgid "Sign" msgid "Sign"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2425 bal/gui/qt/lists.py:675 #: bal/gui/qt/dialogs.py:2426 bal/gui/qt/lists.py:675
msgid "Broadcast" msgid "Broadcast"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2432 bal/gui/qt/lists.py:677 #: bal/gui/qt/dialogs.py:2433 bal/gui/qt/lists.py:677
msgid "Invalidate" msgid "Invalidate"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2437 bal/gui/qt/lists.py:674 #: bal/gui/qt/dialogs.py:2438 bal/gui/qt/lists.py:674
msgid "Merge" msgid "Merge"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2445 #: bal/gui/qt/dialogs.py:2446
msgid "Expiration date: {}" msgid "Expiration date: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2451 #: bal/gui/qt/dialogs.py:2452
msgid "Valid Txs:{}" msgid "Valid Txs:{}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2453 #: bal/gui/qt/dialogs.py:2454
msgid "Total Txs:{}" msgid "Total Txs:{}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2507 #: bal/gui/qt/dialogs.py:2508
msgid "Hide replaced" msgid "Hide replaced"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2509 #: bal/gui/qt/dialogs.py:2510
msgid "Unhide replaced" msgid "Unhide replaced"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2515 #: bal/gui/qt/dialogs.py:2516
msgid "Hide invalidated" msgid "Hide invalidated"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2517 #: bal/gui/qt/dialogs.py:2518
msgid "Unhide invalidated" msgid "Unhide invalidated"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2542 #: bal/gui/qt/dialogs.py:2543
msgid "Will-Executor Service List" msgid "Will-Executor Service List"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2589 bal/gui/qt/lists.py:314 #: bal/gui/qt/dialogs.py:2590 bal/gui/qt/lists.py:314
msgid "New Heir" msgid "New Heir"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2631 #: bal/gui/qt/dialogs.py:2632
msgid "All" msgid "All"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2634 #: bal/gui/qt/dialogs.py:2635
msgid "Valid NC" msgid "Valid NC"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2771 bal/gui/qt/dialogs.py:2847 #: bal/gui/qt/dialogs.py:2773 bal/gui/qt/dialogs.py:2851
msgid "Auto" msgid "Auto"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2773 #: bal/gui/qt/dialogs.py:2775
msgid "Automatically advance through the QR codes." msgid "Automatically advance through the QR codes."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2777 #: bal/gui/qt/dialogs.py:2779
msgid "QR codes per second:" msgid "QR codes per second:"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2781 #: bal/gui/qt/dialogs.py:2783
msgid " /s" msgid " /s"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2783 #: bal/gui/qt/dialogs.py:2785
msgid "Loop" msgid "Loop"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2785 #: bal/gui/qt/dialogs.py:2787
msgid "When the last QR code is reached, keep cycling from the first one instead of stopping." msgid "When the last QR code is reached, keep cycling from the first one instead of stopping."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2795 #: bal/gui/qt/dialogs.py:2797
msgid "QR code size:" msgid "QR code size:"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2841 #: bal/gui/qt/dialogs.py:2845
msgid "Stop" msgid "Stop"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2862 #: bal/gui/qt/dialogs.py:2866
msgid "" msgid ""
"Scan the QR codes below with the will-opening device.\n" "Scan the QR codes below with the will-opening device.\n"
"{} codes carry the whole transfer (any order works, duplicates are ignored)." "{} codes carry the whole transfer (any order works, duplicates are ignored)."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2884 #: bal/gui/qt/dialogs.py:2888
msgid "Frame {} of {}" msgid "Frame {} of {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2907 #: bal/gui/qt/dialogs.py:2911
msgid "Export will" msgid "Export will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2917 #: bal/gui/qt/dialogs.py:2922
msgid "No will transaction to export." msgid "No will transaction to export."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2924 #: bal/gui/qt/dialogs.py:2929
msgid "Export:" msgid "Export:"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2931 #: bal/gui/qt/dialogs.py:2936
msgid "Whole will" msgid "Whole will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2934 #: bal/gui/qt/dialogs.py:2939
msgid "Export the full will items (all details/statuses) as JSON, or only the serialized transactions." msgid "Export the full will items (all details/statuses) as JSON, or only the serialized transactions."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2945 #: bal/gui/qt/dialogs.py:2950
msgid "Send as:" msgid "Send as:"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2947 bal/gui/qt/dialogs.py:3184 #: bal/gui/qt/dialogs.py:2952 bal/gui/qt/dialogs.py:3208
msgid "File" msgid "File"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2948 bal/gui/qt/dialogs.py:3185 #: bal/gui/qt/dialogs.py:2953 bal/gui/qt/dialogs.py:3209
msgid "QR Code" msgid "QR Code"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2949 bal/gui/qt/dialogs.py:3186 #: bal/gui/qt/dialogs.py:2954 bal/gui/qt/dialogs.py:3210
msgid "Audio" msgid "Audio"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2990 #: bal/gui/qt/dialogs.py:3005
msgid "Export the will as a JSON file. Uncheck \"Whole will\" above to export only the serialized transactions (comma-separated)." msgid "Export the will as a JSON file. Uncheck \"Whole will\" above to export only the serialized transactions (comma-separated)."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:2999 #: bal/gui/qt/dialogs.py:3014
msgid "Export…" msgid "Export…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3016 bal/gui/qt/dialogs.py:3242 #: bal/gui/qt/dialogs.py:3032 bal/gui/qt/dialogs.py:3271
#: bal/gui/qt/window.py:1787 bal/gui/qt/window.py:1800 #: bal/gui/qt/window.py:1787 bal/gui/qt/window.py:1800
msgid "Audio MODEM plugin is not available." msgid "Audio MODEM plugin is not available."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3020 bal/gui/qt/dialogs.py:3246 #: bal/gui/qt/dialogs.py:3036 bal/gui/qt/dialogs.py:3275
msgid "Speed (KB/sec):" msgid "Speed (KB/sec):"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3031 #: bal/gui/qt/dialogs.py:3047
msgid "Send" msgid "Send"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3076 bal/gui/qt/dialogs.py:3110 #: bal/gui/qt/dialogs.py:3100 bal/gui/qt/dialogs.py:3134
#: bal/gui/qt/dialogs.py:3135 #: bal/gui/qt/dialogs.py:3159
msgid "No will transaction matches the selected filter." msgid "No will transaction matches the selected filter."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3099 #: bal/gui/qt/dialogs.py:3123
msgid "item(s)" msgid "item(s)"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3099 #: bal/gui/qt/dialogs.py:3123
msgid "transaction(s)" msgid "transaction(s)"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3101 #: bal/gui/qt/dialogs.py:3125
msgid "{} {} will be exported." msgid "{} {} will be exported."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3104 #: bal/gui/qt/dialogs.py:3128
msgid "{} {} will be sent." msgid "{} {} will be sent."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3128 bal/gui/qt/dialogs.py:3131 #: bal/gui/qt/dialogs.py:3152 bal/gui/qt/dialogs.py:3155
#: bal/gui/qt/dialogs.py:3282 bal/gui/qt/dialogs.py:3285 #: bal/gui/qt/dialogs.py:3311 bal/gui/qt/dialogs.py:3314
msgid "Invalid audio speed." msgid "Invalid audio speed."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3167 #: bal/gui/qt/dialogs.py:3191
msgid "Import will" msgid "Import will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3173 #: bal/gui/qt/dialogs.py:3197
msgid "" msgid ""
"Choose how the will was exported: from a file, QR codes or audio.\n" "Choose how the will was exported: from a file, QR codes or audio.\n"
"The import never touches the live will." "The import never touches the live will."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3182 #: bal/gui/qt/dialogs.py:3206
msgid "Import from:" msgid "Import from:"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3218 #: bal/gui/qt/dialogs.py:3246
msgid "Import the JSON will file written by the Export ▶ File option. The will opens in a read-only preview window." msgid "Import the JSON will file written by the Export ▶ File option. The will opens in a read-only preview window."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3225 #: bal/gui/qt/dialogs.py:3253
msgid "Choose will file…" msgid "Choose will file…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3255 #: bal/gui/qt/dialogs.py:3284
msgid "Waiting for the audio transfer…" msgid "Waiting for the audio transfer…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3258 #: bal/gui/qt/dialogs.py:3287
msgid "Receive by audio…" msgid "Receive by audio…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3299 #: bal/gui/qt/dialogs.py:3328
msgid "Receiving…" msgid "Receiving…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3319 #: bal/gui/qt/dialogs.py:3348
msgid "No transaction data received." msgid "No transaction data received."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3337 #: bal/gui/qt/dialogs.py:3366
msgid "Waiting for audio ({:.1f} kbps)…" msgid "Waiting for audio ({:.1f} kbps)…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3410 #: bal/gui/qt/dialogs.py:3439
msgid "Could not parse a transferred will item: {}" msgid "Could not parse a transferred will item: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3430 #: bal/gui/qt/dialogs.py:3459
msgid "Could not parse a transferred transaction: {}" msgid "Could not parse a transferred transaction: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3441 #: bal/gui/qt/dialogs.py:3470
msgid "The imported will contains no valid transaction in this wallet." msgid "The imported will contains no valid transaction in this wallet."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3452 #: bal/gui/qt/dialogs.py:3481
msgid "{} imported transaction(s) are not valid in this wallet and were skipped." msgid "{} imported transaction(s) are not valid in this wallet and were skipped."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3552 #: bal/gui/qt/dialogs.py:3581
msgid "" msgid ""
"Show the will QR codes to the camera, one after another.\n" "Show the will QR codes to the camera, one after another.\n"
"After the first code, every new code is captured automatically.\n" "After the first code, every new code is captured automatically.\n"
"The first frame sets the total number of codes; duplicates are ignored." "The first frame sets the total number of codes; duplicates are ignored."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3562 bal/gui/qt/dialogs.py:3740 #: bal/gui/qt/dialogs.py:3591 bal/gui/qt/dialogs.py:3769
msgid "Waiting for the first frame…" msgid "Waiting for the first frame…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3579 #: bal/gui/qt/dialogs.py:3608
msgid "…or paste/type the frame text here" msgid "…or paste/type the frame text here"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3583 #: bal/gui/qt/dialogs.py:3612
msgid "Add frame" msgid "Add frame"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3589 bal/gui/qt/dialogs.py:3832 #: bal/gui/qt/dialogs.py:3618 bal/gui/qt/dialogs.py:3861
msgid "Scan with camera…" msgid "Scan with camera…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3591 #: bal/gui/qt/dialogs.py:3620
msgid "Start the live camera. While it runs, every new QR code shown to the camera is captured automatically." msgid "Start the live camera. While it runs, every new QR code shown to the camera is captured automatically."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3598 #: bal/gui/qt/dialogs.py:3627
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3605 #: bal/gui/qt/dialogs.py:3634
msgid "Review and Sign…" msgid "Review and Sign…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3624 #: bal/gui/qt/dialogs.py:3653
msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; scan the first code again." msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; scan the first code again."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3715 #: bal/gui/qt/dialogs.py:3744
msgid "All {} frames stored." msgid "All {} frames stored."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3721 #: bal/gui/qt/dialogs.py:3750
msgid "Stored {} of {} frames." msgid "Stored {} of {} frames."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3786 #: bal/gui/qt/dialogs.py:3815
msgid "Cannot start QR scanner, no usable camera found." msgid "Cannot start QR scanner, no usable camera found."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3791 #: bal/gui/qt/dialogs.py:3820
msgid "Stop scanning" msgid "Stop scanning"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3838 #: bal/gui/qt/dialogs.py:3867
msgid "Camera error: {}" msgid "Camera error: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3884 #: bal/gui/qt/dialogs.py:3913
msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; show the first code again." msgid "The scanned code belongs to a different transfer ({} frames, {}). The import was reset; show the first code again."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3928 #: bal/gui/qt/dialogs.py:3957
msgid "The transferred will contains no transactions." msgid "The transferred will contains no transactions."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3957 #: bal/gui/qt/dialogs.py:3986
msgid "Review and sign imported will" msgid "Review and sign imported will"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:3969 #: bal/gui/qt/dialogs.py:3998
msgid "Enter your wallet password to sign the imported transactions." msgid "Enter your wallet password to sign the imported transactions."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4016 #: bal/gui/qt/dialogs.py:4045
msgid "Skip" msgid "Skip"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4032 #: bal/gui/qt/dialogs.py:4061
msgid "Save signed file…" msgid "Save signed file…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4035 #: bal/gui/qt/dialogs.py:4064
msgid "Show signed QR…" msgid "Show signed QR…"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4059 #: bal/gui/qt/dialogs.py:4088
msgid "Transaction {} of {}" msgid "Transaction {} of {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4061 #: bal/gui/qt/dialogs.py:4090
msgid "TXID: {}" msgid "TXID: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4064 #: bal/gui/qt/dialogs.py:4093
msgid "unknown" msgid "unknown"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4070 #: bal/gui/qt/dialogs.py:4099
msgid "(no outputs)" msgid "(no outputs)"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4084 #: bal/gui/qt/dialogs.py:4113
msgid "unknown (partial transaction)" msgid "unknown (partial transaction)"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4086 #: bal/gui/qt/dialogs.py:4115
msgid "" msgid ""
"Total outputs: {}\n" "Total outputs: {}\n"
"Fee: {}" "Fee: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4097 #: bal/gui/qt/dialogs.py:4126
msgid "" msgid ""
"Signed {} of {} transactions.\n" "Signed {} of {} transactions.\n"
"\n" "\n"
"Save a signed file to carry to the broadcast device, or show the signed transactions as QR codes." "Save a signed file to carry to the broadcast device, or show the signed transactions as QR codes."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4116 #: bal/gui/qt/dialogs.py:4145
msgid "Could not sign the transaction: {}" msgid "Could not sign the transaction: {}"
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4135 #: bal/gui/qt/dialogs.py:4164
msgid "Signed will saved." msgid "Signed will saved."
msgstr "" msgstr ""
#: bal/gui/qt/dialogs.py:4146 #: bal/gui/qt/dialogs.py:4175
msgid "No signed transaction to show." msgid "No signed transaction to show."
msgstr "" msgstr ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
# ./make-release.sh # read version from bal/manifest.json # ./make-release.sh # read version from bal/manifest.json
# ./make-release.sh v0.6.2 # bump manifest to 0.6.2, then release # ./make-release.sh v0.6.2 # bump manifest to 0.6.2, then release
# #
# Requires: git, gpg, curl, python3, sha256sum # Requires: git, gpg, curl, python3, sha256sum, Babel for python3 (pip install babel)
# Optional: ruff (lint skipped if not installed) # Optional: ruff (lint skipped if not installed)
# Credentials: ~/.git-credentials or GITEA_USER / GITEA_TOKEN env vars # Credentials: ~/.git-credentials or GITEA_USER / GITEA_TOKEN env vars
@@ -33,6 +33,12 @@ else
NEW_VER="" NEW_VER=""
fi fi
# Babel is needed by the i18n tests (step 3) and by build_zip.py, which
# compiles the translation catalogs into the zip (step 5). Check it up front
# so a release never stops half-way.
python3 -c "import babel" 2>/dev/null \
|| die "Babel is missing for $(command -v python3): pip install babel"
echo "=== Release ${TAG} ===" echo "=== Release ${TAG} ==="
echo "Current manifest version: ${CURRENT_VER}" echo "Current manifest version: ${CURRENT_VER}"
[ -n "$NEW_VER" ] && echo "New version (will bump): ${NEW_VER}" [ -n "$NEW_VER" ] && echo "New version (will bump): ${NEW_VER}"
@@ -68,6 +74,8 @@ if QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src python3 -m pytest \
tests/test_group_d_alarms.py tests/test_group_e_mock_karen7.py \ tests/test_group_d_alarms.py tests/test_group_e_mock_karen7.py \
tests/test_group_f_heir_change_rebuild.py tests/test_group_g_basic_calendar.py \ tests/test_group_f_heir_change_rebuild.py tests/test_group_g_basic_calendar.py \
tests/test_group_h_v048.py \ tests/test_group_h_v048.py \
tests/test_i18n.py tests/test_translations.py \
tests/test_cli_*.py tests/test_import_will_details.py \
-q 2>&1; then -q 2>&1; then
echo "All tests passed." echo "All tests passed."
else else

View File

@@ -6,6 +6,12 @@ target-version = "py312"
select =["E", "W", "F", "I", "N", "B", "INT"] # INT: gettext rules (see bal/i18n.py) select =["E", "W", "F", "I", "N", "B", "INT"] # INT: gettext rules (see bal/i18n.py)
ignore = ["E501"] ignore = ["E501"]
[tool.ruff.lint.isort]
# Classify the plugin package explicitly: otherwise ruff decides from where it
# is run / how the project root is detected, and the same import block is
# "sorted" on one machine and I001 on another.
known-first-party = ["bal"]
[tool.ruff.lint.pep8-naming] [tool.ruff.lint.pep8-naming]
classmethod-decorators = ["classmethod", "classproperty"] # electrum.util.classproperty uses cls classmethod-decorators = ["classmethod", "classproperty"] # electrum.util.classproperty uses cls

View File

@@ -0,0 +1,136 @@
"""
Stored will data must not depend on the GUI language (rule in bal/i18n.py).
Covers the two findings of the i18n review:
* the status history of a new will item is saved as English "New" (not
``_("New")``), and histories saved by test builds as "Nuovo"/"Nuevo" are
mapped back to "New" on load;
* the push helper no longer writes transient fields (``txs``, ``txsids`` and
the translated ``broadcast_status``) into the will item's executor record,
and such fields saved by older versions are dropped on load.
Run (standalone or with pytest):
QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src \
python3 tests/test_core_status_language_neutral.py
"""
import os
import re
import sys
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
from electrum.transaction import Transaction
from bal.core.will import WillItem
from bal.core.willexecutors import Willexecutors
# WillItem parses a raw tx and calls add_info_from_wallet; no live wallet here.
_patcher = patch.object(Transaction, "add_info_from_wallet")
_patcher.start()
ROOT = Path(__file__).resolve().parent.parent
_VALID_TX_HEX = (
"01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65b"
"f38633b424eb4031000000006c493046022100a82bbc57a0136751e543"
"3f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7d"
"e89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501"
"2102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae3"
"5cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a"
"42146f11ef8414ae929feaafc388ac00000000"
)
URL = "https://we.example"
def _item(status="", we=None):
return WillItem(
{"tx": _VALID_TX_HEX, "heirs": {}, "willexecutor": we, "status": status}
)
# ------------------------------------------------------------------ #
# Status history "New"
# ------------------------------------------------------------------ #
def test_builders_store_english_new():
"""window.py and the CLI controller must not translate the stored status."""
for rel in ("bal/gui/qt/window.py", "bal/cli/controller.py"):
src = (ROOT / rel).read_text(encoding="utf-8")
assert re.search(r'tx\["status"\]\s*=\s*"New"', src), rel
assert not re.search(r'tx\["status"\]\s*=\s*_\(', src), rel
def test_translated_new_is_mapped_back_on_load():
assert _item("Nuovo.Signed.Pushed").status == "New.Signed.Pushed"
assert _item("Nuevo").status == "New"
assert _item("New.Signed").status == "New.Signed"
def test_only_the_leading_new_is_touched():
assert _item(".Nuovo").status == ".Nuovo"
assert _item("Signed.Nuovo").status == "Signed.Nuovo"
def test_translated_new_labels_match_the_catalogs():
"""Every catalog's translation of "New" must be recognised on load."""
found = set()
for po in (ROOT / "bal" / "locale").glob("*/LC_MESSAGES/bal.po"):
text = po.read_text(encoding="utf-8")
m = re.search(r'^msgid "New"\nmsgstr "(.*)"$', text, re.MULTILINE)
if m and m.group(1):
found.add(m.group(1))
assert found, "no catalog translates 'New'"
assert found <= set(WillItem.TRANSLATED_NEW_LABELS), found
# ------------------------------------------------------------------ #
# Transient push fields
# ------------------------------------------------------------------ #
class FakeItem:
def __init__(self):
self.we = {"url": URL, "selected": True, "address": "a", "base_fee": 1}
self.tx = "rawtx"
def get_status(self, name):
return name in ("VALID", "COMPLETE")
def test_push_helper_does_not_modify_the_will_item():
item = FakeItem()
before = dict(item.we)
out = Willexecutors.get_willexecutor_transactions({"t1": item})
assert out[URL]["txsids"] == ["t1"]
assert "broadcast_status" in out[URL]
assert item.we == before
assert out[URL] is not item.we
def test_transient_fields_saved_by_older_versions_are_dropped_on_load():
we = {
"url": URL,
"selected": True,
"txs": "raw\n",
"txsids": ["t1"],
"broadcast_status": "Riuscito",
}
item = _item("New", we)
assert item.we == {"url": URL, "selected": True}
assert "broadcast_status" not in item.to_dict()["willexecutor"]
def test_clean_executor_record_is_kept_as_is():
we = {"url": URL, "selected": True}
assert _item("New", we).we is we
if __name__ == "__main__":
for name in sorted(dir()):
if name.startswith("test_"):
globals()[name]()
print(f" [OK] {name}")
print("[OK] All language-neutral status tests passed")