Skip to content

Lang units settings#536

Open
epicleafies wants to merge 16 commits intobitcoin-core:qt6from
epicleafies:lang-units-settings
Open

Lang units settings#536
epicleafies wants to merge 16 commits intobitcoin-core:qt6from
epicleafies:lang-units-settings

Conversation

@epicleafies
Copy link
Copy Markdown
Contributor

@epicleafies epicleafies commented Mar 16, 2026

Added the option to change the display unit under display settings, can choose between BTC or sats. #512
Added the option to change language under display settings, currently only English and Spanish are translated.
All instances of bitcoin values are now tagged so the change with display unit settings (s for sats).
All text now tagged so that it will change with language (with the exception of wallet names).
Added unit, qml and functional tests for display units and language.

Screenshot from 2026-03-25 14-55-43 Screenshot from 2026-03-25 14-56-50 Screenshot from 2026-03-25 14-57-26

@hebasto
Copy link
Copy Markdown
Member

hebasto commented Mar 23, 2026

Please rebase.

Copy link
Copy Markdown
Collaborator

@johnny9 johnny9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 2dd9024

I tested this out and it works well. For follow ups, we should get the system defaults to be the actual language of the system its running on and not just english. Should also tie BitcoinAmount's units with the option.

}

CoreText {
Loader {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think loaders aren't really needed.

Component {
id: satoshiIconComponent
CoreText {
text: "s"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the label for sats is going to be sat/sats

Add `language` and `displayUnit` Q_PROPERTYs to OptionsQmlModel with
QSettings persistence so user preferences survive restarts.

`buildAvailableLanguages()` scans `:/translations/` for `bitcoin_*.qm`
resource files at runtime, so the list grows automatically as new locale
files are added. `languageLabel()` returns a human-readable string with
both the native name and English name (e.g., "Español — Spanish").
`displayUnitLabel()` returns "BTC" or "sat" for the active unit.

A dedicated settings_keys.h header centralises the QSettings key
constants so they remain consistent between reads and writes.
Introduce a `qml/locale/` directory containing Qt Linguist source files
for the QML app's own strings:
  - `bitcoin_qml.ts` — source-language template (all strings, no translations)
  - `bitcoin_qml_es.ts` — Spanish translations

Update CMakeLists.txt to compile `bitcoin_qml_es.ts` → `bitcoin_qml_es.qm`
and embed it under the `:/translations/` resource prefix so
`buildAvailableLanguages()` can discover it at runtime.

Also declare all 100+ bitcoin-qt locale `.qm` files as GENERATED
resources so the app can embed the full set of upstream translations.
`add_dependencies(bitcoinqml bitcoin-qt)` ensures these files are
compiled before the resource step runs.
`Transaction::prettyAmount()` now accepts a `display_unit` parameter
(0 = BTC, 1 = sat) and delegates to `QmlBitcoinUnits::format()`, so
amounts render in whichever unit the user has selected.

`ActivityListModel` stores the active display unit in `m_display_unit`
and emits `dataChanged()` for all rows when it changes so the wallet
activity list updates immediately without a refresh. `WalletQmlModel`
exposes `setDisplayUnit()` and cascades the value down to the list model.

The QML wallet pages (Activity, RequestPayment, Send) add a `Binding`
that keeps their local unit in sync with `optionsModel.displayUnit`,
so switching units in settings takes effect everywhere at once.

Also fixes a typo: `subsctribeToCoreSignals` → `subscribeToCoreSignals`
in activitylistmodel.cpp.
Wrap hardcoded string literals in BlockClock.qml, main.qml, and
NodeSettings.qml with qsTr() so Qt's translation machinery can
substitute them when a non-default language is active.

No logic changes; this is purely a string-marking pass to make these
strings available for extraction and translation.
On startup, install two QTranslator objects into the application:
  1. The bitcoin-qt translator for C++ strings from the bitcoin submodule
  2. A QML-app-specific translator for strings in this repo's QML files

The active language is read from `optionsModel.language()`; an empty
string means "use the system locale". The `-lang` CLI flag allows
overriding the persisted preference for a single session.

When `OptionsQmlModel` emits `languageChanged`, the translators are
swapped out and `engine.retranslate()` is called so all `qsTr()` bindings
in running QML are re-evaluated immediately without a restart.
Add two new settings pages reachable from the Display settings screen:

  SettingsDisplayUnit: radio-button pair (BTC / sat) that writes to
  `optionsModel.displayUnit`; the activity list and amount inputs
  update immediately via the bindings added in the previous commits.

  SettingsLanguage: scrollable list of all available languages with a
  search filter; selecting an entry sets `optionsModel.language`, which
  triggers the translator swap introduced in the previous commit.

Update SettingsDisplay.qml to surface both pages as Setting rows whose
description text reflects the current selection, register the new QML
files in bitcoin_qml.qrc, and update qml_test_harness.py to accept
extra_args so functional tests can pass CLI flags (e.g. -lang).

Tests added:
  - test_display_settings.cpp: C++ unit tests covering settings
    persistence and SAT/BTC amount formatting edge cases.
  - tst_displaysettings.qml: QML unit tests for OptionButton binding
    logic and mutual exclusion.
  - qml_test_settings_display.py: end-to-end functional tests for
    unit switching and language switching (including Spanish UI strings).
When the display unit is set to SAT, amount input fields (Send,
RequestPayment, BitcoinAmountInputField) and the wallet badge balance
now display "s" as the unit prefix instead of "sat". A Loader component
switches between the BTC ("₿") and SAT ("s") labels dynamically.
<common/args.h> and <common/system.h> were included twice near the
top of the file. Remove the redundant second copies.
The glob bitcoin_*.qm matches bitcoin_qml_es.qm, which after
stripping the prefix and suffix yields the tag "qml_es". QLocale
cannot resolve that tag (nativeLanguageName() returns empty), so
languageLabel() falls back to the raw string "qml_es" as a visible
entry in the language picker. Guard against this by skipping any
tag that begins with "qml_".
ButtonGroup's managed-exclusive checked behaviour conflicts with
the declarative `checked: optionsModel.displayUnit === X` bindings.
The QML unit tests (tst_displaysettings.qml) already document and
work around this by omitting ButtonGroup entirely. Align the
production component with the tested pattern. Mutual exclusion is
guaranteed by the model: only one condition can be true at a time.
Issue bitcoin-core#512 requires verifying that language and display-unit
selections survive a restart. Add a second phase to
qml_test_settings_display.py that sets SAT + Spanish, stops the
first harness (keeping the datadir), relaunches without
-resetguisettings, and asserts both settings are still active.

To support this pattern, extend QmlTestHarness with:
  - reset_settings=True (default): controls -resetguisettings
  - datadir=None: reuse an existing datadir instead of creating one
  - stop(cleanup=True): skip shutil.rmtree when cleanup=False so
    the datadir survives for the second harness

Also fix the copyright year in tst_displaysettings.qml (2025->2026).
BitcoinAmount::unitLabel() always returned "sat" in SAT mode regardless
of the current value. Pluralize it so amounts other than ±1 satoshi show
"sats" and exactly ±1 shows "sat".

Also emit unitChanged() from setSatoshi() and clear() when the label
would flip between singular and plural, so QML bindings to unitLabel
update reactively without requiring an explicit display refresh.
Replace the Loader + Component pattern used to switch the unit label
between "₿" and "s" with a direct CoreText binding to unitLabel. Now
that unitLabel is a reactive property that emits unitChanged() on
unit or amount changes, no dynamic component loading is needed.

Also update the placeholder text to show "0" in SAT mode and
"0.00000000" in BTC mode, matching the respective input formats.
WalletBadge previously showed "s" as a fixed satoshi unit symbol.
Add a balanceSatoshi property (typed as qint64 via Q_PROPERTY so the
full 64-bit value reaches QML) and wire it from DesktopWallets so the
badge can pluralize correctly: "sat" for exactly 1 satoshi, "sats"
otherwise. The balance is also moved to a suffix position to match
standard display conventions.

Add displayUnitLabelForAmount() to OptionsQmlModel as a testable helper
for the pluralization logic, and update the QML unit tests accordingly.
WalletQmlModelTransaction previously returned raw satoshi integers from
amount(), fee(), and total(), so the review page showed unformatted
numbers with no unit label.

Add display unit awareness to WalletQmlModelTransaction: a
setDisplayUnit() method stores the active unit and re-emits the
relevant changed signals, and a formatWithUnit() helper formats values
using QmlBitcoinUnits with the correct "sat"/"sats"/"₿" suffix.
WalletQmlModel cascades the display unit to the current transaction
both when the transaction is created and when the unit changes.

Also append the unit label to per-recipient amounts in MultipleSendReview.
@epicleafies epicleafies force-pushed the lang-units-settings branch from c3b688f to bfe3359 Compare April 1, 2026 16:16
Copy link
Copy Markdown
Collaborator

@johnny9 johnny9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK bfe3359

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants