Summary
Regenerating the Qt translation template (src/qt/locale/bitcoin_en.xlf) previously used sequential _msg<N> IDs. When strings moved within the sorted message list, IDs shifted, creating large diffs and making translation platforms treat unchanged strings as new.
This PR switches XLF <trans-unit id=...> values to stable IDs derived from a SHA256 hash of the message context and source text.
Details
As mentioned in #33224 (comment), any change that moves a translatable string within the sorted messages can renumber subsequent entries when IDs are sequential.
Some English texts can appear in multiple places (e.g. “Clear”) with different meanings depending on the Qt context. To avoid collisions, the hash includes the <group resname=...> context.
Fix
The translate target already runs contrib/devtools/stabilize_xlf_ids.py. Instead of trying to preserve/renumber _msg<N> IDs by matching old/new units, the script now rewrites every <trans-unit id> to:
sha256(resname + "\0" + source-text)(hex)- keeping the existing plural
[N]suffix for plural forms
The script also checks for duplicate IDs and aborts if any are detected.
Note: this is a one-time mass change from _msg<N> IDs to hashes; after that, rerunning translate on an unchanged tree should not churn IDs.
With this PR applied, IDs become stable hashes so ordering changes no longer cause renumbering churn.
Reproducer
You can test the replacer by changing any translatable text:
0diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
1--- a/src/qt/bitcoingui.cpp (revision a38b8cf788922174538161cda81ce28f2ac462ec)
2+++ b/src/qt/bitcoingui.cpp (date 1767310724675)
3@@ -258,7 +258,7 @@
4 connect(modalOverlay, &ModalOverlay::triggered, tabGroup, &QActionGroup::setEnabled);
5
6 overviewAction = new QAction(platformStyle->SingleColorIcon(":/icons/overview"), tr("&Overview"), this);
7- overviewAction->setStatusTip(tr("Show general overview of wallet"));
8+ overviewAction->setStatusTip(tr("Show general overview of Wallet"));
9 overviewAction->setToolTip(overviewAction->statusTip());
10 overviewAction->setCheckable(true);
11 overviewAction->setShortcut(QKeySequence(QStringLiteral("Alt+1")));
and regenerating the translations
0cmake --preset dev-mode -DWITH_USDT=OFF && cmake --build build_dev_mode --target translate
You will notice that the new text moved to a different place with a different hash, but all other messages retained their IDs.