Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
031e67b
qt: add language and display unit options to OptionsQmlModel
epicleafies Mar 16, 2026
e458650
build: add QML translation compilation
epicleafies Mar 16, 2026
8e6f962
qt: add display unit support to transaction and activity list
epicleafies Mar 16, 2026
db824ad
qt: wrap UI strings with qsTr() for i18n
epicleafies Mar 16, 2026
25bec98
qt: install translators on startup and support -lang flag
epicleafies Mar 16, 2026
d26dabf
qt: add Display Unit and Language settings pages
epicleafies Mar 16, 2026
d851fdb
qt: show "s" as satoshi unit symbol in wallet amount fields
epicleafies Mar 16, 2026
7b78bc0
qt: remove duplicate includes in bitcoin.cpp
epicleafies Mar 16, 2026
d89bedc
qt: skip qml_* locale tags when building available language list
epicleafies Mar 16, 2026
a32f93a
qt: remove ButtonGroup from SettingsDisplayUnit
epicleafies Mar 16, 2026
e45a598
test: add restart-persistence test for display settings
epicleafies Mar 16, 2026
7fa429a
qt: pluralize sat/sats unit label based on amount
epicleafies Mar 31, 2026
2015123
qt: simplify unit label in amount input fields
epicleafies Mar 31, 2026
085bea3
qt: show sat/sats balance with pluralization in wallet badge
epicleafies Mar 31, 2026
1433347
qt: format send review amounts with display unit
epicleafies Mar 31, 2026
364dbbe
qt: remove qsTr() from debug.log filename
epicleafies May 12, 2026
ca475c5
build: regenerate translation source files
epicleafies May 12, 2026
d2f2c9c
qt: Replace raw exception with translated error in signer status
epicleafies May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/gui-functional-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,4 @@ jobs:
python3 test/functional/qml_test_peers.py
python3 test/functional/qml_test_proxy.py
python3 test/functional/qml_test_debug_log.py
python3 test/functional/qml_test_settings_display.py
44 changes: 44 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ endif()
set(QML_QRC "${CMAKE_CURRENT_SOURCE_DIR}/qml/bitcoin_qml.qrc")
qt6_add_resources(QML_QRC_CPP ${QML_QRC})
list(APPEND QML_SOURCES ${QML_QRC_CPP})

list(APPEND QML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/bitcoin/src/init/bitcoin-qt.cpp")

# Build QML library
Expand Down Expand Up @@ -116,6 +117,49 @@ target_link_libraries(bitcoinqml
Qt6::Widgets
)

# Compile QML-app-specific translations (.ts → .qm) and embed as resources.
qt6_add_translation(QML_GUI_QM_FILES
${CMAKE_CURRENT_SOURCE_DIR}/qml/locale/bitcoin_qml_es.ts
)
set_source_files_properties(${QML_GUI_QM_FILES} PROPERTIES
QT_RESOURCE_ALIAS "bitcoin_qml_es.qm"
)
qt6_add_resources(bitcoinqml "qml_gui_translations"
PREFIX "/translations"
FILES ${QML_GUI_QM_FILES}
)

# Embed bitcoin-qt translations built by the bitcoin submodule.
# The submodule (BUILD_GUI=ON) compiles .ts → .qm files into
# ${CMAKE_BINARY_DIR}/bitcoin/src/qt/locale/ at build time.
# We mark each file as GENERATED so CMake does not require it to exist
# at configure time, and add a dependency on the bitcoin-qt target so
# the .qm files are built before the resource compiler runs.
set(BITCOIN_LOCALE_TAGS
am ar ast_ES ay az@latin az be bg bn br bs ca cmn cs cy da
de_AT de_CH de el en eo es_CL es_CO es_DO es es_SV es_VE et eu
fa fil fi fo fr_CM fr_LU fr ga_IE ga gd gl_ES gl gu hak ha he hi
hr hu id is it ja ka kk@latin kk kl km kn ko ku_IQ ku ky la lb
lt lv mg mi mk ml mn mr_IN mr ms mt my nb ne nl no or pam pa pl
ps pt_BR pt ro ru sa sc sd si sk sl sm sn so sq
sr@ijekavianlatin sr@latin sr sv sw szl ta te th tk tl tn tr ug uk ur
uz@Cyrl uz@Latn uz ve vi yi yo yue zh_CN zh-Hans zh-Hant zh_HK zh zh_TW zu
)
set(BITCOIN_QT_QM_FILES)
foreach(tag IN LISTS BITCOIN_LOCALE_TAGS)
set(qm_file "${CMAKE_BINARY_DIR}/bitcoin/src/qt/locale/bitcoin_${tag}.qm")
set_source_files_properties(${qm_file} PROPERTIES
GENERATED TRUE
QT_RESOURCE_ALIAS "bitcoin_${tag}.qm"
)
list(APPEND BITCOIN_QT_QM_FILES ${qm_file})
endforeach()
qt6_add_resources(bitcoinqml "bitcoin_qt_translations"
PREFIX "/translations"
FILES ${BITCOIN_QT_QM_FILES}
)
add_dependencies(bitcoinqml bitcoin-qt)

# Put it all together
add_executable(bitcoin-core-app main.cpp)
target_include_directories(bitcoin-core-app
Expand Down
47 changes: 45 additions & 2 deletions qml/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
#include <common/init.h>
#include <common/system.h>
#include <chainparams.h>
#include <common/args.h>
#include <common/system.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/init.h>
Expand Down Expand Up @@ -68,8 +66,10 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QSettings>
#include <QString>
#include <QStyleHints>
#include <QTranslator>
#include <QUrl>

QT_BEGIN_NAMESPACE
Expand Down Expand Up @@ -224,6 +224,28 @@ int QmlGuiMain(int argc, char* argv[])
app.setOrganizationDomain(QAPP_ORG_DOMAIN);
app.setApplicationName(QAPP_APP_NAME_DEFAULT);

// Manages translators swapped on runtime language changes.
// app_translator: bitcoin-qt strings (shared C++ layer)
// qml_translator: QML-app-specific strings
std::unique_ptr<QTranslator> app_translator;
std::unique_ptr<QTranslator> qml_translator;
const auto reset_translator = [](std::unique_ptr<QTranslator>& t) {
if (t) { QCoreApplication::removeTranslator(t.get()); t.reset(); }
};
const auto install_language = [&](const QString& lang) {
reset_translator(app_translator);
reset_translator(qml_translator);
if (!lang.isEmpty()) {
auto t = std::make_unique<QTranslator>();
if (t->load(QStringLiteral(":/translations/bitcoin_%1.qm").arg(lang)))
{ QCoreApplication::installTranslator(t.get()); app_translator = std::move(t); }

auto tq = std::make_unique<QTranslator>();
if (tq->load(QStringLiteral(":/translations/bitcoin_qml_%1.qm").arg(lang)))
{ QCoreApplication::installTranslator(tq.get()); qml_translator = std::move(tq); }
}
};

// Parse command-line options. We do this after qt in order to show an error if there are problems parsing these.
SetupServerArgs(gArgs, init->canListenIpc());

Expand Down Expand Up @@ -353,6 +375,27 @@ int QmlGuiMain(int argc, char* argv[])
engine.rootContext()->setContextProperty("optionsModel", &options_model);
engine.rootContext()->setContextProperty("needOnboarding", need_onboarding);

// -lang CLI flag overrides the persisted setting (bitcoin-qt compatibility).
// Must be after gArgs.ParseParameters() and after setupChainQSettings() so
// QSettings targets the correct chain-specific file.
// Unlike bitcoin-qt which treats -lang as session-only, this persists the
// selection so subsequent launches continue using the CLI-specified language.
// To reset to system default, use the Settings UI or pass -lang= (empty).
const QString cli_lang = QString::fromStdString(gArgs.GetArg("-lang", ""));
if (!cli_lang.isEmpty()) {
options_model.setLanguage(cli_lang);
}

// Install language before QML engine loads so that all qsTr() calls in QML
// pick up the correct locale from the start.
install_language(options_model.language());

// Retranslate the QML UI immediately when the user picks a new language.
QObject::connect(&options_model, &OptionsQmlModel::languageChanged, [&]() {
install_language(options_model.language());
engine.retranslate();
});

AppMode app_mode = SetupAppMode();
BuildInfo build_info;
Clipboard clipboard;
Expand Down
2 changes: 2 additions & 0 deletions qml/bitcoin_qml.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
<file>pages/onboarding/OnboardingStrengthen.qml</file>
<file>pages/onboarding/OnboardingWizard.qml</file>
<file>pages/settings/SettingsAbout.qml</file>
<file>pages/settings/SettingsDisplayUnit.qml</file>
<file>pages/settings/SettingsLanguage.qml</file>
<file>pages/settings/SettingsBlockClockDisplayMode.qml</file>
<file>pages/settings/SettingsConnection.qml</file>
<file>pages/settings/SettingsDebugLog.qml</file>
Expand Down
7 changes: 6 additions & 1 deletion qml/bitcoinamount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ qint64 BitcoinAmount::satoshi() const
void BitcoinAmount::setSatoshi(qint64 new_amount)
{
if (m_satoshi != new_amount || !m_isSet) {
const bool label_changes = (m_unit == Unit::SAT) &&
((qAbs(m_satoshi) == 1) != (qAbs(new_amount) == 1));
m_isSet = true;
m_satoshi = new_amount;
if (label_changes) Q_EMIT unitChanged();
Q_EMIT amountChanged();
}
}
Expand All @@ -54,8 +57,10 @@ void BitcoinAmount::clear()
if (!m_isSet && m_satoshi == 0) {
return;
}
const bool label_changes = (m_unit == Unit::SAT) && (qAbs(m_satoshi) == 1);
m_satoshi = 0;
m_isSet = false;
if (label_changes) Q_EMIT unitChanged();
Q_EMIT amountChanged();
}

Expand All @@ -75,7 +80,7 @@ QString BitcoinAmount::unitLabel() const
{
switch (m_unit) {
case Unit::BTC: return "₿";
case Unit::SAT: return "sat";
case Unit::SAT: return (qAbs(m_satoshi) == 1) ? QStringLiteral("sat") : QStringLiteral("sats");
}
assert(false);
}
Expand Down
4 changes: 2 additions & 2 deletions qml/components/BitcoinAmountInputField.qml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ ColumnLayout {
color: Theme.color.neutral9
placeholderTextColor: enabled ? Theme.color.neutral7 : Theme.color.neutral4
background: Item {}
placeholderText: "0.00000000"
placeholderText: root.amount && root.amount.unit === BitcoinAmount.SAT ? "0" : "0.00000000"
selectByMouse: true

text: root.amount ? root.amount.display : ""
Expand Down Expand Up @@ -98,7 +98,7 @@ ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
text: root.amount ? root.amount.unitLabel : ""
font.pixelSize: 18
color: enabled ? Theme.color.neutral7 : Theme.color.neutral4
color: root.enabled ? Theme.color.neutral7 : Theme.color.neutral4
}

Icon {
Expand Down
4 changes: 2 additions & 2 deletions qml/components/BlockClock.qml
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,9 @@ Item {
name: "CONNECTING"; when: !paused && !connected
PropertyChanges {
target: root
header: "Connecting"
header: qsTr("Connecting")
headerSize: dial.width * (3/25)
subText: "Please wait"
subText: qsTr("Please wait")
estimating: false
}
PropertyChanges {
Expand Down
Loading
Loading