Update base translations and improve consistency (#12432)

* Improve confirmation prompts and tooltips for delete actions in the GUI

* Fixes #10543
This commit is contained in:
Jonathan White
2025-09-07 20:16:36 -04:00
committed by GitHub
parent b12f6f0786
commit 7ea141652e
18 changed files with 250 additions and 176 deletions

View File

@@ -844,16 +844,6 @@
<source>Use Virtual Keyboard</source> <source>Use Virtual Keyboard</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>&lt;p&gt;You can use advanced search queries to find any entry in your open databases. The following shortcuts are useful:&lt;br/&gt;
Ctrl+F - Toggle database search&lt;br/&gt;
Ctrl+1 - Type username&lt;br/&gt;
Ctrl+2 - Type password&lt;br/&gt;
Ctrl+3 - Type TOTP&lt;br/&gt;
Ctrl+4 - Type URL&lt;br/&gt;
Ctrl+5 - Use Virtual Keyboard (Windows Only)&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Type {URL}</source> <source>Type {URL}</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -862,6 +852,25 @@ Ctrl+5 - Use Virtual Keyboard (Windows Only)&lt;/p&gt;</source>
<source>Copy URL</source> <source>Copy URL</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>&lt;p&gt;The following shortcuts are available:&lt;br/&gt;
Ctrl+F - Focus search&lt;br/&gt;
Ctrl+1 - Type username&lt;br/&gt;
Ctrl+2 - Type password&lt;br/&gt;
Ctrl+3 - Type TOTP&lt;br/&gt;
Ctrl+4 - Type URL&lt;br/&gt;
Ctrl+5 - Use Virtual Keyboard (Windows Only)&lt;br/&gt;
Ctrl+Shift+1 - Copy username&lt;br/&gt;
Ctrl+Shift+2 - Copy password&lt;br/&gt;
Ctrl+Shift+3 - Copy TOTP&lt;br/&gt;
Ctrl+Shift+4 - Copy URL&lt;br/&gt;
&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>You can use advanced search queries to find any entry in your open databases.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>BrowserAccessControlDialog</name> <name>BrowserAccessControlDialog</name>
@@ -2638,18 +2647,6 @@ This is definitely a bug, please report it to the developers.</source>
<source>Remember my choice</source> <source>Remember my choice</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Delete group</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Do you really want to delete the group &quot;%1&quot; for good?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Move group to recycle bin?</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Do you really want to move the group &quot;%1&quot; to the recycle bin?</source> <source>Do you really want to move the group &quot;%1&quot; to the recycle bin?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -2748,10 +2745,6 @@ Disable safe saves and try again?</source>
<source>KeePass 2 Database</source> <source>KeePass 2 Database</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Save database backup</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Empty recycle bin?</source> <source>Empty recycle bin?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -2861,6 +2854,22 @@ Disable safe saves and try again?</source>
<source>Save</source> <source>Save</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Save Database Backup</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Confirm Delete Group</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Do you really want to permanently delete the group &quot;%1&quot;?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Confirm Recycle Group</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>EditEntryAttachmentsDialog</name> <name>EditEntryAttachmentsDialog</name>
@@ -4472,10 +4481,6 @@ This will leave your passwords and sensitive information vulnerable!
<source>name (descending)</source> <source>name (descending)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>unknown</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Export database to HTML file</source> <source>Export database to HTML file</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -4484,6 +4489,10 @@ This will leave your passwords and sensitive information vulnerable!
<source>HTML file</source> <source>HTML file</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>invalid sort order</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>FdoSecrets::DBusMgr</name> <name>FdoSecrets::DBusMgr</name>
@@ -5718,10 +5727,6 @@ Are you sure you want to continue with this file?</source>
<source>&amp;Edit Entry</source> <source>&amp;Edit Entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>&amp;Delete Entry</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>&amp;New Group</source> <source>&amp;New Group</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -6087,10 +6092,6 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
<source>Merge From Database</source> <source>Merge From Database</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Create Entry</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Edit Entry</source> <source>Edit Entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -6335,10 +6336,6 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
<source>Password Generator</source> <source>Password Generator</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>E&amp;xpire Entry</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Clear SSH Agent</source> <source>Clear SSH Agent</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -6347,6 +6344,40 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
<source>Clear all identities in ssh-agent</source> <source>Clear all identities in ssh-agent</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>New Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Edit Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>E&amp;xpire Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Expire Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Delete Entry</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<source>Move selected entry(s) to the recycle bin</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message numerus="yes">
<source>Permanently delete the selected entry(s)</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
</context> </context>
<context> <context>
<name>ManageDatabase</name> <name>ManageDatabase</name>
@@ -6741,10 +6772,6 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
</context> </context>
<context> <context>
<name>PasskeyExporter</name> <name>PasskeyExporter</name>
<message>
<source>KeePassXC: Passkey Export</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>File &quot;%1.passkey&quot; already exists. <source>File &quot;%1.passkey&quot; already exists.
Do you want to overwrite it? Do you want to overwrite it?
@@ -6763,6 +6790,10 @@ Do you want to overwrite it?
<source>Cannot write to file</source> <source>Cannot write to file</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Overwrite Existing File?</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>PasskeyImportDialog</name> <name>PasskeyImportDialog</name>
@@ -6837,14 +6868,6 @@ Do you want to overwrite it?
<source>Cannot open file &quot;%1&quot; for reading.</source> <source>Cannot open file &quot;%1&quot; for reading.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Open passkey file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot import passkey</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Cannot import passkey file &quot;%1&quot;. Data is missing.</source> <source>Cannot import passkey file &quot;%1&quot;. Data is missing.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -6859,6 +6882,14 @@ The following data is missing:
<source>Cannot import passkey file &quot;%1&quot;. Private key is missing or malformed.</source> <source>Cannot import passkey file &quot;%1&quot;. Private key is missing or malformed.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Open Passkey File</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Passkey Import Failed</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>PasswordEditWidget</name> <name>PasswordEditWidget</name>
@@ -7938,10 +7969,6 @@ Do you want to overwrite it?</source>
<source>Average password length</source> <source>Average password length</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>%1 characters</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Word count for the diceware passphrase.</source> <source>Word count for the diceware passphrase.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -8805,24 +8832,6 @@ Kernel: %3 %4</source>
<numerusform></numerusform> <numerusform></numerusform>
</translation> </translation>
</message> </message>
<message>
<source>Do you really want to delete the entry &quot;%1&quot; for good?</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<source>Do you really want to delete %n entry(s) for good?</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message numerus="yes">
<source>Delete entry(s)?</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message> <message>
<source>Do you really want to move entry &quot;%1&quot; to the recycle bin?</source> <source>Do you really want to move entry &quot;%1&quot; to the recycle bin?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -8834,17 +8843,6 @@ Kernel: %3 %4</source>
<numerusform></numerusform> <numerusform></numerusform>
</translation> </translation>
</message> </message>
<message numerus="yes">
<source>Move entry(s) to recycle bin?</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message>
<source>Replace references to entry?</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes"> <message numerus="yes">
<source>Entry &quot;%1&quot; has %2 reference(s). Do you want to overwrite references with values, skip this entry, or delete anyway?</source> <source>Entry &quot;%1&quot; has %2 reference(s). Do you want to overwrite references with values, skip this entry, or delete anyway?</source>
<translation type="unfinished"> <translation type="unfinished">
@@ -9221,17 +9219,6 @@ This option is deprecated, use --set-key-file instead.</source>
<source>Proton Pass Import</source> <source>Proton Pass Import</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Delete plugin data?</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<source>Delete plugin data from Entry(s)?</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message> <message>
<source>Passkey</source> <source>Passkey</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -9278,6 +9265,53 @@ This option is deprecated, use --set-key-file instead.</source>
<source>Fit</source> <source>Fit</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message numerus="yes">
<source>%1 character(s)</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message>
<source>Do you really want to permanently delete the entry &quot;%1&quot;?</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<source>Do you really want to permanently delete %n entry(s)?</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message numerus="yes">
<source>Confirm Delete Entry(s)</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message numerus="yes">
<source>Confirm Recycle Entry(s)</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message>
<source>Confirm Delete Plugin Data</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<source>Delete plugin data from the selected entry(s)?</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message>
<source>Confirm Replace Entry References</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>QtIOCompressor</name> <name>QtIOCompressor</name>
@@ -9822,14 +9856,17 @@ This option is deprecated, use --set-key-file instead.</source>
<source>Average password length</source> <source>Average password length</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>%1 characters</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Average password length is less than ten characters. Longer passwords provide more security.</source> <source>Average password length is less than ten characters. Longer passwords provide more security.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message numerus="yes">
<source>%1 character(s)</source>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
</context> </context>
<context> <context>
<name>SSHAgent</name> <name>SSHAgent</name>
@@ -10505,10 +10542,6 @@ Example: JBSWY3DPEHPK3PXP</source>
</context> </context>
<context> <context>
<name>YubiKeyInterfacePCSC</name> <name>YubiKeyInterfacePCSC</name>
<message>
<source>The YubiKey PCSC interface has not been initialized.</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Could not find or access hardware key with serial number %1. Please present it to continue. </source> <source>Could not find or access hardware key with serial number %1. Please present it to continue. </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -10521,10 +10554,6 @@ Example: JBSWY3DPEHPK3PXP</source>
<source>Hardware key was not found or is not configured.</source> <source>Hardware key was not found or is not configured.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Failed to complete a challenge-response, the PCSC error code was: %1</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>(NFC) %1 [%2] - Slot %3, %4</source> <source>(NFC) %1 [%2] - Slot %3, %4</source>
<comment>YubiKey display fields</comment> <comment>YubiKey display fields</comment>
@@ -10540,13 +10569,17 @@ Example: JBSWY3DPEHPK3PXP</source>
<comment>USB Challenge-Response Key no interaction required</comment> <comment>USB Challenge-Response Key no interaction required</comment>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>The YubiKey PC/SC interface has not been initialized.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to complete a challenge-response, the PC/SC error code was: %1</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>YubiKeyInterfaceUSB</name> <name>YubiKeyInterfaceUSB</name>
<message>
<source>Unknown</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Press</source> <source>Press</source>
<comment>USB Challenge-Response Key interaction request</comment> <comment>USB Challenge-Response Key interaction request</comment>
@@ -10587,5 +10620,10 @@ Example: JBSWY3DPEHPK3PXP</source>
<comment>YubiKey display fields</comment> <comment>YubiKey display fields</comment>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Unknown</source>
<comment>Unknown hardware key name</comment>
<translation type="unfinished"></translation>
</message>
</context> </context>
</TS> </TS>

View File

@@ -50,14 +50,19 @@
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;p&gt;You can use advanced search queries to find any entry in your open databases. The following shortcuts are useful:&lt;br/&gt; <string>&lt;p&gt;The following shortcuts are available:&lt;br/&gt;
Ctrl+F - Toggle database search&lt;br/&gt; Ctrl+F - Focus search&lt;br/&gt;
Ctrl+1 - Type username&lt;br/&gt; Ctrl+1 - Type username&lt;br/&gt;
Ctrl+2 - Type password&lt;br/&gt; Ctrl+2 - Type password&lt;br/&gt;
Ctrl+3 - Type TOTP&lt;br/&gt; Ctrl+3 - Type TOTP&lt;br/&gt;
Ctrl+4 - Type URL&lt;br/&gt; Ctrl+4 - Type URL&lt;br/&gt;
Ctrl+5 - Use Virtual Keyboard (Windows Only)&lt;/p&gt;</string> Ctrl+5 - Use Virtual Keyboard (Windows Only)&lt;br/&gt;
Ctrl+Shift+1 - Copy username&lt;br/&gt;
Ctrl+Shift+2 - Copy password&lt;br/&gt;
Ctrl+Shift+3 - Copy TOTP&lt;br/&gt;
Ctrl+Shift+4 - Copy URL&lt;br/&gt;
&lt;/p&gt;</string>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QToolButton { <string notr="true">QToolButton {
@@ -173,6 +178,9 @@ Ctrl+5 - Use Virtual Keyboard (Windows Only)&lt;/p&gt;</string>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="toolTip">
<string>You can use advanced search queries to find any entry in your open databases.</string>
</property>
<property name="placeholderText"> <property name="placeholderText">
<string>Search…</string> <string>Search…</string>
</property> </property>

View File

@@ -67,8 +67,8 @@ int DatabaseInfo::executeWithDatabase(QSharedPointer<Database> database, QShared
out << QObject::tr("Number of short passwords") << ": " << QString::number(stats.shortPasswords) << Qt::endl; out << QObject::tr("Number of short passwords") << ": " << QString::number(stats.shortPasswords) << Qt::endl;
out << QObject::tr("Number of weak passwords") << ": " << QString::number(stats.weakPasswords) << Qt::endl; out << QObject::tr("Number of weak passwords") << ": " << QString::number(stats.weakPasswords) << Qt::endl;
out << QObject::tr("Entries excluded from reports") << ": " << QString::number(stats.excludedEntries) << Qt::endl; out << QObject::tr("Entries excluded from reports") << ": " << QString::number(stats.excludedEntries) << Qt::endl;
out << QObject::tr("Average password length") << ": " << QObject::tr("%1 characters").arg(stats.averagePwdLength()) out << QObject::tr("Average password length") << ": "
<< Qt::endl; << QObject::tr("%1 character(s)", "", stats.averagePwdLength()).arg(stats.averagePwdLength()) << Qt::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@@ -151,23 +151,23 @@ namespace Tools
if (seconds >= secondsInYear) { if (seconds >= secondsInYear) {
auto years = std::floor(seconds / secondsInYear); auto years = std::floor(seconds / secondsInYear);
return QObject::tr("over %1 year(s)", nullptr, years).arg(years); return QObject::tr("over %1 year(s)", "", years).arg(years);
} else if (seconds >= secondsInMonth) { } else if (seconds >= secondsInMonth) {
auto months = std::round(seconds / secondsInMonth); auto months = std::round(seconds / secondsInMonth);
return QObject::tr("about %1 month(s)", nullptr, months).arg(months); return QObject::tr("about %1 month(s)", "", months).arg(months);
} else if (seconds >= secondsInWeek) { } else if (seconds >= secondsInWeek) {
auto weeks = std::round(seconds / secondsInWeek); auto weeks = std::round(seconds / secondsInWeek);
return QObject::tr("%1 week(s)", nullptr, weeks).arg(weeks); return QObject::tr("%1 week(s)", "", weeks).arg(weeks);
} else if (seconds >= secondsInDay) { } else if (seconds >= secondsInDay) {
auto days = std::floor(seconds / secondsInDay); auto days = std::floor(seconds / secondsInDay);
return QObject::tr("%1 day(s)", nullptr, days).arg(days); return QObject::tr("%1 day(s)", "", days).arg(days);
} else if (seconds >= secondsInHour) { } else if (seconds >= secondsInHour) {
auto hours = std::floor(seconds / secondsInHour); auto hours = std::floor(seconds / secondsInHour);
return QObject::tr("%1 hour(s)", nullptr, hours).arg(hours); return QObject::tr("%1 hour(s)", "", hours).arg(hours);
} }
auto minutes = std::floor(seconds / 60); auto minutes = std::floor(seconds / 60);
return QObject::tr("%1 minute(s)", nullptr, minutes).arg(minutes); return QObject::tr("%1 minute(s)", "", minutes).arg(minutes);
} }
bool readFromDevice(QIODevice* device, QByteArray& data, int size) bool readFromDevice(QIODevice* device, QByteArray& data, int size)

View File

@@ -1120,8 +1120,8 @@ void DatabaseWidget::deleteGroup()
if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) { if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) {
auto result = MessageBox::question( auto result = MessageBox::question(
this, this,
tr("Delete group"), tr("Confirm Delete Group"),
tr("Do you really want to delete the group \"%1\" for good?").arg(currentGroup->name().toHtmlEscaped()), tr("Do you really want to permanently delete the group \"%1\"?").arg(currentGroup->name().toHtmlEscaped()),
MessageBox::Delete | MessageBox::Cancel, MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel); MessageBox::Cancel);
@@ -1130,7 +1130,7 @@ void DatabaseWidget::deleteGroup()
} }
} else { } else {
auto result = MessageBox::question(this, auto result = MessageBox::question(this,
tr("Move group to recycle bin?"), tr("Confirm Recycle Group"),
tr("Do you really want to move the group " tr("Do you really want to move the group "
"\"%1\" to the recycle bin?") "\"%1\" to the recycle bin?")
.arg(currentGroup->name().toHtmlEscaped()), .arg(currentGroup->name().toHtmlEscaped()),
@@ -2640,7 +2640,7 @@ bool DatabaseWidget::saveBackup()
} }
const QString newFilePath = fileDialog()->getSaveFileName(this, const QString newFilePath = fileDialog()->getSaveFileName(this,
tr("Save database backup"), tr("Save Database Backup"),
FileDialog::getLastDir("backup", oldFilePath), FileDialog::getLastDir("backup", oldFilePath),
tr("KeePass 2 Database").append(" (*.kdbx)")); tr("KeePass 2 Database").append(" (*.kdbx)"));

View File

@@ -32,14 +32,15 @@ namespace GuiTools
if (permanent) { if (permanent) {
QString prompt; QString prompt;
if (entries.size() == 1) { if (entries.size() == 1) {
prompt = QObject::tr("Do you really want to delete the entry \"%1\" for good?") auto entry = entries.first();
.arg(entries.first()->title().toHtmlEscaped()); prompt = QObject::tr("Do you really want to permanently delete the entry \"%1\"?")
.arg(entry->resolvePlaceholder(entry->title()).toHtmlEscaped());
} else { } else {
prompt = QObject::tr("Do you really want to delete %n entry(s) for good?", "", entries.size()); prompt = QObject::tr("Do you really want to permanently delete %n entry(s)?", "", entries.size());
} }
auto answer = MessageBox::question(parent, auto answer = MessageBox::question(parent,
QObject::tr("Delete entry(s)?", "", entries.size()), QObject::tr("Confirm Delete Entry(s)", "", entries.size()),
prompt, prompt,
MessageBox::Delete | MessageBox::Cancel, MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel); MessageBox::Cancel);
@@ -50,14 +51,15 @@ namespace GuiTools
} else { } else {
QString prompt; QString prompt;
if (entries.size() == 1) { if (entries.size() == 1) {
auto entry = entries.first();
prompt = QObject::tr("Do you really want to move entry \"%1\" to the recycle bin?") prompt = QObject::tr("Do you really want to move entry \"%1\" to the recycle bin?")
.arg(entries.first()->title().toHtmlEscaped()); .arg(entry->resolvePlaceholder(entry->title()).toHtmlEscaped());
} else { } else {
prompt = QObject::tr("Do you really want to move %n entry(s) to the recycle bin?", "", entries.size()); prompt = QObject::tr("Do you really want to move %n entry(s) to the recycle bin?", "", entries.size());
} }
auto answer = MessageBox::question(parent, auto answer = MessageBox::question(parent,
QObject::tr("Move entry(s) to recycle bin?", "", entries.size()), QObject::tr("Confirm Recycle Entry(s)", "", entries.size()),
prompt, prompt,
MessageBox::Move | MessageBox::Cancel, MessageBox::Move | MessageBox::Cancel,
MessageBox::Cancel); MessageBox::Cancel);
@@ -72,11 +74,12 @@ namespace GuiTools
return false; return false;
} }
auto answer = MessageBox::question(parent, auto answer =
QObject::tr("Delete plugin data?"), MessageBox::question(parent,
QObject::tr("Delete plugin data from Entry(s)?", "", entries.size()), QObject::tr("Confirm Delete Plugin Data"),
MessageBox::Delete | MessageBox::Cancel, QObject::tr("Delete plugin data from the selected entry(s)?", "", entries.size()),
MessageBox::Cancel); MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
return answer == MessageBox::Delete; return answer == MessageBox::Delete;
} }
@@ -100,7 +103,7 @@ namespace GuiTools
// Prompt the user on what to do with the reference (Overwrite, Delete, Skip) // Prompt the user on what to do with the reference (Overwrite, Delete, Skip)
auto result = MessageBox::question( auto result = MessageBox::question(
parent, parent,
QObject::tr("Replace references to entry?"), QObject::tr("Confirm Replace Entry References"),
QObject::tr( QObject::tr(
"Entry \"%1\" has %2 reference(s). " "Entry \"%1\" has %2 reference(s). "
"Do you want to overwrite references with values, skip this entry, or delete anyway?", "Do you want to overwrite references with values, skip this entry, or delete anyway?",

View File

@@ -944,6 +944,17 @@ void MainWindow::updateMenuActionState()
m_ui->actionEntryEdit->setEnabled(singleEntrySelected); m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
m_ui->actionEntryExpire->setEnabled(multiEntrySelected); m_ui->actionEntryExpire->setEnabled(multiEntrySelected);
m_ui->actionEntryDelete->setEnabled(multiEntrySelected); m_ui->actionEntryDelete->setEnabled(multiEntrySelected);
if (dbWidget) {
if (dbWidget->database()->metadata()->recycleBinEnabled() && !inRecycleBin) {
m_ui->actionEntryDelete->setToolTip(
tr("Move selected entry(s) to the recycle bin", "", dbWidget->numberOfSelectedEntries()));
} else {
m_ui->actionEntryDelete->setToolTip(
tr("Permanently delete the selected entry(s)", "", dbWidget->numberOfSelectedEntries()));
}
} else {
m_ui->actionEntryDelete->setToolTip(tr("Delete Entry"));
}
m_ui->actionEntryRestore->setVisible(multiEntrySelected && inRecycleBin); m_ui->actionEntryRestore->setVisible(multiEntrySelected && inRecycleBin);
m_ui->actionEntryRestore->setEnabled(multiEntrySelected && inRecycleBin); m_ui->actionEntryRestore->setEnabled(multiEntrySelected && inRecycleBin);
if (dbWidget) { if (dbWidget) {

View File

@@ -532,7 +532,7 @@
<string>&amp;New Entry…</string> <string>&amp;New Entry…</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Create Entry</string> <string>New Entry</string>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string notr="true">Ctrl+N</string> <string notr="true">Ctrl+N</string>
@@ -542,6 +542,9 @@
<property name="text"> <property name="text">
<string>&amp;Edit Entry…</string> <string>&amp;Edit Entry…</string>
</property> </property>
<property name="iconText">
<string>Edit Entry…</string>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Edit Entry</string> <string>Edit Entry</string>
</property> </property>
@@ -554,12 +557,21 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>E&amp;xpire Entry</string> <string>E&amp;xpire Entry</string>
</property>
<property name="iconText">
<string>Expire Entry</string>
</property>
<property name="toolTip">
<string>Expire Entry</string>
</property> </property>
</action> </action>
<action name="actionEntryDelete"> <action name="actionEntryDelete">
<property name="text"> <property name="text">
<string>&amp;Delete Entry</string> <string>&amp;Delete Entry</string>
</property>
<property name="iconText">
<string>Delete Entry</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Delete Entry</string> <string>Delete Entry</string>

View File

@@ -1690,21 +1690,21 @@ void EditEntryWidget::deleteAllHistoryEntries()
QMenu* EditEntryWidget::createPresetsMenu() QMenu* EditEntryWidget::createPresetsMenu()
{ {
auto* expirePresetsMenu = new QMenu(this); auto* expirePresetsMenu = new QMenu(this);
expirePresetsMenu->addAction(tr("%n hour(s)", nullptr, 12))->setData(QVariant::fromValue(TimeDelta::fromHours(12))); expirePresetsMenu->addAction(tr("%n hour(s)", "", 12))->setData(QVariant::fromValue(TimeDelta::fromHours(12)));
expirePresetsMenu->addAction(tr("%n hour(s)", nullptr, 24))->setData(QVariant::fromValue(TimeDelta::fromHours(24))); expirePresetsMenu->addAction(tr("%n hour(s)", "", 24))->setData(QVariant::fromValue(TimeDelta::fromHours(24)));
expirePresetsMenu->addSeparator(); expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7))); expirePresetsMenu->addAction(tr("%n week(s)", "", 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7)));
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromDays(14))); expirePresetsMenu->addAction(tr("%n week(s)", "", 2))->setData(QVariant::fromValue(TimeDelta::fromDays(14)));
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromDays(21))); expirePresetsMenu->addAction(tr("%n week(s)", "", 3))->setData(QVariant::fromValue(TimeDelta::fromDays(21)));
expirePresetsMenu->addSeparator(); expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromMonths(1))); expirePresetsMenu->addAction(tr("%n month(s)", "", 1))->setData(QVariant::fromValue(TimeDelta::fromMonths(1)));
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromMonths(2))); expirePresetsMenu->addAction(tr("%n month(s)", "", 2))->setData(QVariant::fromValue(TimeDelta::fromMonths(2)));
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3))); expirePresetsMenu->addAction(tr("%n month(s)", "", 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3)));
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6))); expirePresetsMenu->addAction(tr("%n month(s)", "", 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6)));
expirePresetsMenu->addSeparator(); expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromYears(1))); expirePresetsMenu->addAction(tr("%n year(s)", "", 1))->setData(QVariant::fromValue(TimeDelta::fromYears(1)));
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromYears(2))); expirePresetsMenu->addAction(tr("%n year(s)", "", 2))->setData(QVariant::fromValue(TimeDelta::fromYears(2)));
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromYears(3))); expirePresetsMenu->addAction(tr("%n year(s)", "", 3))->setData(QVariant::fromValue(TimeDelta::fromYears(3)));
return expirePresetsMenu; return expirePresetsMenu;
} }

View File

@@ -548,7 +548,7 @@ void EntryView::startDrag(Qt::DropActions supportedActions)
for (auto& index : selectedIndexes) { for (auto& index : selectedIndexes) {
if (++i > 4) { if (++i > 4) {
int remaining = selectedIndexes.size() - i + 1; int remaining = selectedIndexes.size() - i + 1;
listWidget.addItem(tr("+ %1 entry(s)...", nullptr, remaining).arg(remaining)); listWidget.addItem(tr("+ %1 entry(s)...", "", remaining).arg(remaining));
break; break;
} }

View File

@@ -55,8 +55,10 @@ QString ExportDialog::getStrategyName(ExportSortingStrategy strategy)
return tr("name (ascending)"); return tr("name (ascending)");
case ExportSortingStrategy::BY_NAME_DESC: case ExportSortingStrategy::BY_NAME_DESC:
return tr("name (descending)"); return tr("name (descending)");
default:
Q_ASSERT(false);
return tr("invalid sort order");
} }
return tr("unknown");
} }
void ExportDialog::exportDatabase() void ExportDialog::exportDatabase()

View File

@@ -76,7 +76,7 @@ void PasskeyExporter::exportSelectedEntry(const Entry* entry, const QString& fol
const auto fullPath = QString("%1/%2.passkey").arg(folder, Tools::cleanFilename(entry->title())); const auto fullPath = QString("%1/%2.passkey").arg(folder, Tools::cleanFilename(entry->title()));
if (QFile::exists(fullPath)) { if (QFile::exists(fullPath)) {
auto dialogResult = MessageBox::warning(m_parent, auto dialogResult = MessageBox::warning(m_parent,
tr("KeePassXC: Passkey Export"), tr("Overwrite Existing File?"),
tr("File \"%1.passkey\" already exists.\n" tr("File \"%1.passkey\" already exists.\n"
"Do you want to overwrite it?\n") "Do you want to overwrite it?\n")
.arg(entry->title()), .arg(entry->title()),

View File

@@ -39,7 +39,7 @@ void PasskeyImporter::importPasskey(QSharedPointer<Database>& database, Entry* e
{ {
auto filter = QString("%1 (*.passkey);;%2 (*)").arg(tr("Passkey file"), tr("All files")); auto filter = QString("%1 (*.passkey);;%2 (*)").arg(tr("Passkey file"), tr("All files"));
auto fileName = auto fileName =
fileDialog()->getOpenFileName(m_parent, tr("Open passkey file"), FileDialog::getLastDir("passkey"), filter); fileDialog()->getOpenFileName(m_parent, tr("Open Passkey File"), FileDialog::getLastDir("passkey"), filter);
if (fileName.isEmpty()) { if (fileName.isEmpty()) {
return; return;
} }
@@ -62,7 +62,7 @@ void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>&
const auto passkeyObject = browserMessageBuilder()->getJsonObject(fileData); const auto passkeyObject = browserMessageBuilder()->getJsonObject(fileData);
if (passkeyObject.isEmpty()) { if (passkeyObject.isEmpty()) {
MessageBox::information(m_parent, MessageBox::information(m_parent,
tr("Cannot import passkey"), tr("Passkey Import Failed"),
tr("Cannot import passkey file \"%1\". Data is missing.").arg(file.fileName())); tr("Cannot import passkey file \"%1\". Data is missing.").arg(file.fileName()));
return; return;
} }
@@ -73,14 +73,14 @@ void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>&
QStringList() << "relyingParty" << "url" << "username" << "credentialId" << "userHandle" << "privateKey"); QStringList() << "relyingParty" << "url" << "username" << "credentialId" << "userHandle" << "privateKey");
if (!missingKeys.isEmpty()) { if (!missingKeys.isEmpty()) {
MessageBox::information(m_parent, MessageBox::information(m_parent,
tr("Cannot import passkey"), tr("Passkey Import Failed"),
tr("Cannot import passkey file \"%1\".\nThe following data is missing:\n%2") tr("Cannot import passkey file \"%1\".\nThe following data is missing:\n%2")
.arg(file.fileName(), missingKeys.join(", "))); .arg(file.fileName(), missingKeys.join(", ")));
} else if (!privateKey.startsWith(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_START) } else if (!privateKey.startsWith(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_START)
|| !privateKey.trimmed().endsWith(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_END)) { || !privateKey.trimmed().endsWith(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_END)) {
MessageBox::information( MessageBox::information(
m_parent, m_parent,
tr("Cannot import passkey"), tr("Passkey Import Failed"),
tr("Cannot import passkey file \"%1\". Private key is missing or malformed.").arg(file.fileName())); tr("Cannot import passkey file \"%1\". Private key is missing or malformed.").arg(file.fileName()));
} else { } else {
const auto relyingParty = passkeyObject["relyingParty"].toString(); const auto relyingParty = passkeyObject["relyingParty"].toString();

View File

@@ -122,7 +122,7 @@ void ReportsWidgetStatistics::calculateStats()
tr("Excluding entries from reports, e. g. because they are known to have a poor password, isn't " tr("Excluding entries from reports, e. g. because they are known to have a poor password, isn't "
"necessarily a problem but you should keep an eye on them.")); "necessarily a problem but you should keep an eye on them."));
addStatsRow(tr("Average password length"), addStatsRow(tr("Average password length"),
tr("%1 characters").arg(stats->averagePwdLength()), tr("%1 character(s)", "", stats->averagePwdLength()).arg(stats->averagePwdLength()),
stats->isAvgPwdTooShort(), stats->isAvgPwdTooShort(),
tr("Average password length is less than ten characters. Longer passwords provide more security.")); tr("Average password length is less than ten characters. Longer passwords provide more security."));
} }

View File

@@ -43,7 +43,7 @@ YubiKey::YubiKey()
connect(YubiKeyInterfacePCSC::instance(), SIGNAL(challengeStarted()), this, SIGNAL(challengeStarted())); connect(YubiKeyInterfacePCSC::instance(), SIGNAL(challengeStarted()), this, SIGNAL(challengeStarted()));
connect(YubiKeyInterfacePCSC::instance(), SIGNAL(challengeCompleted()), this, SIGNAL(challengeCompleted())); connect(YubiKeyInterfacePCSC::instance(), SIGNAL(challengeCompleted()), this, SIGNAL(challengeCompleted()));
} else { } else {
qDebug("YubiKey: PCSC interface is disabled or not initialized."); qDebug("YubiKey: PC/SC interface is disabled or not initialized.");
} }
m_initialized = num_interfaces > 0; m_initialized = num_interfaces > 0;

View File

@@ -519,7 +519,7 @@ YubiKeyInterfacePCSC::YubiKeyInterfacePCSC()
: YubiKeyInterface() : YubiKeyInterface()
{ {
if (ensureValidContext(m_sc_context) != SCARD_S_SUCCESS) { if (ensureValidContext(m_sc_context) != SCARD_S_SUCCESS) {
qDebug("YubiKey: Failed to establish PCSC context."); qDebug("YubiKey: Failed to establish PC/SC context.");
} else { } else {
m_initialized = true; m_initialized = true;
} }
@@ -528,7 +528,7 @@ YubiKeyInterfacePCSC::YubiKeyInterfacePCSC()
YubiKeyInterfacePCSC::~YubiKeyInterfacePCSC() YubiKeyInterfacePCSC::~YubiKeyInterfacePCSC()
{ {
if (m_initialized && SCardReleaseContext(m_sc_context) != SCARD_S_SUCCESS) { if (m_initialized && SCardReleaseContext(m_sc_context) != SCARD_S_SUCCESS) {
qDebug("YubiKey: Failed to release PCSC context."); qDebug("YubiKey: Failed to release PC/SC context.");
} }
} }
@@ -678,7 +678,7 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
{ {
m_error.clear(); m_error.clear();
if (!m_initialized) { if (!m_initialized) {
m_error = tr("The YubiKey PCSC interface has not been initialized."); m_error = tr("The YubiKey PC/SC interface has not been initialized.");
return YubiKey::ChallengeResult::YCR_ERROR; return YubiKey::ChallengeResult::YCR_ERROR;
} }
@@ -762,7 +762,7 @@ YubiKey::ChallengeResult YubiKeyInterfacePCSC::performChallenge(void* key,
m_error = tr("Hardware key was not found or is not configured."); m_error = tr("Hardware key was not found or is not configured.");
} else { } else {
m_error = m_error =
tr("Failed to complete a challenge-response, the PCSC error code was: %1").arg(QString::number(rv)); tr("Failed to complete a challenge-response, the PC/SC error code was: %1").arg(QString::number(rv));
} }
return YubiKey::ChallengeResult::YCR_ERROR; return YubiKey::ChallengeResult::YCR_ERROR;

View File

@@ -144,7 +144,7 @@ YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys(int& connectedKeys)
int vid, pid; int vid, pid;
yk_get_key_vid_pid(yk_key.get(), &vid, &pid); yk_get_key_vid_pid(yk_key.get(), &vid, &pid);
QString name = m_pid_names.value(pid, tr("Unknown")); QString name = m_pid_names.value(pid, tr("Unknown", "Unknown hardware key name"));
if (vid == ONLYKEY_VID) { if (vid == ONLYKEY_VID) {
name = QStringLiteral("OnlyKey %ver"); name = QStringLiteral("OnlyKey %ver");
} }

View File

@@ -1026,7 +1026,7 @@ void TestCli::testInfo()
QCOMPARE(m_stdout->readLine(), QByteArray("Number of short passwords: 0\n")); QCOMPARE(m_stdout->readLine(), QByteArray("Number of short passwords: 0\n"));
QCOMPARE(m_stdout->readLine(), QByteArray("Number of weak passwords: 2\n")); QCOMPARE(m_stdout->readLine(), QByteArray("Number of weak passwords: 2\n"));
QCOMPARE(m_stdout->readLine(), QByteArray("Entries excluded from reports: 0\n")); QCOMPARE(m_stdout->readLine(), QByteArray("Entries excluded from reports: 0\n"));
QCOMPARE(m_stdout->readLine(), QByteArray("Average password length: 11 characters\n")); QCOMPARE(m_stdout->readLine(), QByteArray("Average password length: 11 character(s)\n"));
// Test with quiet option. // Test with quiet option.
setInput("a"); setInput("a");