mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-10 03:53:26 -08:00
Merge branch 'master' into tooomm-qt5
This commit is contained in:
commit
94b85b8940
266 changed files with 11392 additions and 930 deletions
|
|
@ -6,8 +6,13 @@ set(CMAKE_AUTOUIC ON)
|
|||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(UTILITY_SOURCES
|
||||
libcockatrice/utility/cryptoutil.cpp libcockatrice/utility/expression.cpp libcockatrice/utility/levenshtein.cpp
|
||||
libcockatrice/utility/passwordhasher.cpp libcockatrice/utility/server_rate_limiter.cpp
|
||||
libcockatrice/utility/cryptoutil.cpp
|
||||
libcockatrice/utility/expression.cpp
|
||||
libcockatrice/utility/levenshtein.cpp
|
||||
libcockatrice/utility/passwordhasher.cpp
|
||||
libcockatrice/utility/report_categories.cpp
|
||||
libcockatrice/utility/server_rate_limiter.cpp
|
||||
libcockatrice/utility/translation_loader.cpp
|
||||
libcockatrice/utility/warning_categories.cpp
|
||||
)
|
||||
|
||||
|
|
@ -20,6 +25,7 @@ set(UTILITY_HEADERS
|
|||
libcockatrice/utility/macros.h
|
||||
libcockatrice/utility/passwordhasher.h
|
||||
libcockatrice/utility/playmat_params.h
|
||||
libcockatrice/utility/report_categories.h
|
||||
libcockatrice/utility/string_limits.h
|
||||
libcockatrice/utility/dice_limits.h
|
||||
libcockatrice/utility/counter_limits.h
|
||||
|
|
@ -27,6 +33,7 @@ set(UTILITY_HEADERS
|
|||
libcockatrice/utility/zone_names.h
|
||||
libcockatrice/utility/days_years_between.h
|
||||
libcockatrice/utility/server_rate_limiter.h
|
||||
libcockatrice/utility/translation_loader.h
|
||||
libcockatrice/utility/warning_categories.h
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
#include "report_categories.h"
|
||||
|
||||
namespace ReportCategories
|
||||
{
|
||||
const QStringList &keys()
|
||||
{
|
||||
static const QStringList categories = {"cheating", "bug_abuse", "harassment", "verbal_abuse",
|
||||
"hate_speech", "spam", "other"};
|
||||
return categories;
|
||||
}
|
||||
|
||||
bool isValid(const QString &key)
|
||||
{
|
||||
return keys().contains(key, Qt::CaseInsensitive);
|
||||
}
|
||||
} // namespace ReportCategories
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef REPORT_CATEGORIES_H
|
||||
#define REPORT_CATEGORIES_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
/**
|
||||
* Canonical report categories, shared by the client and the server.
|
||||
*
|
||||
* This is the single source of truth for the category keys exchanged over the
|
||||
* wire (Command_Report.category) and stored in the reports table. The server
|
||||
* only accepts keys from this list and the client report dialog only offers
|
||||
* keys from this list, so the two sides cannot drift apart again.
|
||||
*/
|
||||
namespace ReportCategories
|
||||
{
|
||||
/** @brief The complete list of valid category keys, in canonical order. */
|
||||
const QStringList &keys();
|
||||
|
||||
/** @brief Whether @a key is a valid report category (case-insensitive). */
|
||||
bool isValid(const QString &key);
|
||||
} // namespace ReportCategories
|
||||
|
||||
#endif // REPORT_CATEGORIES_H
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
#include "translation_loader.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QLibraryInfo>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
#include <QTranslator>
|
||||
#include <algorithm>
|
||||
|
||||
// Kept under the historical category name so existing QT_LOGGING_RULES filters keep working.
|
||||
Q_LOGGING_CATEGORY(TranslationLoaderLog, "qt_translator")
|
||||
|
||||
QList<QString> TranslationLoader::qtTranslationCandidates()
|
||||
{
|
||||
QStringList candidates;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
// Qt 6.8 exposes every path listed in qt.conf, path() only returns the first one.
|
||||
candidates << QLibraryInfo::paths(QLibraryInfo::TranslationsPath);
|
||||
#else
|
||||
candidates << QLibraryInfo::path(QLibraryInfo::TranslationsPath);
|
||||
#endif
|
||||
|
||||
// A relocated Qt can report a prefix-relative path that does not exist, so probe the usual
|
||||
// install layouts as well instead of trusting that single report.
|
||||
const QString prefix = QLibraryInfo::path(QLibraryInfo::PrefixPath);
|
||||
const QString dataPath = QLibraryInfo::path(QLibraryInfo::DataPath);
|
||||
candidates << prefix + QStringLiteral("/share/qt6/translations")
|
||||
<< prefix + QStringLiteral("/share/qt/translations") << prefix + QStringLiteral("/translations")
|
||||
<< dataPath + QStringLiteral("/translations");
|
||||
|
||||
QList<QString> uniqueCandidates;
|
||||
for (const QString &candidate : candidates) {
|
||||
if (!candidate.isEmpty() && !uniqueCandidates.contains(candidate)) {
|
||||
uniqueCandidates << candidate;
|
||||
}
|
||||
}
|
||||
return uniqueCandidates;
|
||||
}
|
||||
|
||||
QList<QString> TranslationLoader::qtTranslationPaths()
|
||||
{
|
||||
QList<QString> existingPaths;
|
||||
for (const QString &candidate : qtTranslationCandidates()) {
|
||||
if (QFileInfo::exists(candidate)) {
|
||||
existingPaths << candidate;
|
||||
}
|
||||
}
|
||||
return existingPaths;
|
||||
}
|
||||
|
||||
QList<QString> TranslationLoader::applicationTranslationPaths(const QString &_installedPath)
|
||||
{
|
||||
// A build tree keeps the .qm files next to the executable, the installed layout puts them in
|
||||
// _installedPath. Searching both keeps a developer build translated as well.
|
||||
const QString executableDirectory = QCoreApplication::applicationDirPath();
|
||||
|
||||
QList<QString> paths;
|
||||
for (const QString &path : {_installedPath, executableDirectory}) {
|
||||
if (!path.isEmpty() && !paths.contains(path)) {
|
||||
paths << path;
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
QList<QString> TranslationLoader::availableLanguages(const QString &_prefix, const QList<QString> &_directories)
|
||||
{
|
||||
const QRegularExpression filePattern(
|
||||
QRegularExpression::anchoredPattern(QRegularExpression::escape(_prefix) + QStringLiteral("_(.*)\\.qm")));
|
||||
|
||||
QSet<QString> languages;
|
||||
for (const QString &directory : _directories) {
|
||||
if (directory.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
const QDir dir(directory);
|
||||
for (const QString &fileName : dir.entryList(QDir::Files)) {
|
||||
const auto match = filePattern.match(fileName);
|
||||
if (match.hasMatch()) {
|
||||
languages.insert(match.captured(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<QString> sortedLanguages(languages.cbegin(), languages.cend());
|
||||
std::sort(sortedLanguages.begin(), sortedLanguages.end());
|
||||
return sortedLanguages;
|
||||
}
|
||||
|
||||
TranslationLoadResult
|
||||
TranslationLoader::loadFrom(QTranslator &_translator, const QString &_nameHint, const QList<QString> &_directories)
|
||||
{
|
||||
bool anyDirectoryExists = false;
|
||||
|
||||
for (const QString &directory : _directories) {
|
||||
if (directory.isEmpty() || !QFileInfo::exists(directory)) {
|
||||
continue;
|
||||
}
|
||||
anyDirectoryExists = true;
|
||||
if (_translator.load(_nameHint, directory)) {
|
||||
return TranslationLoadResult::Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
return anyDirectoryExists ? TranslationLoadResult::Missing : TranslationLoadResult::NoDirectory;
|
||||
}
|
||||
|
||||
TranslationLoadResult TranslationLoader::loadQt(QTranslator &_translator, const QString &_lang)
|
||||
{
|
||||
// An unset language is the default for English, which has no translation file to begin with.
|
||||
if (_lang.isEmpty()) {
|
||||
return TranslationLoadResult::Missing;
|
||||
}
|
||||
|
||||
const QString nameHint = QStringLiteral("qt_") + _lang;
|
||||
const auto result = loadFrom(_translator, nameHint, qtTranslationPaths());
|
||||
|
||||
switch (result) {
|
||||
case TranslationLoadResult::Loaded:
|
||||
qCInfo(TranslationLoaderLog) << "Loaded Qt translation" << nameHint << "at" << _translator.filePath();
|
||||
break;
|
||||
case TranslationLoadResult::Missing:
|
||||
qCDebug(TranslationLoaderLog)
|
||||
<< "No Qt translation" << nameHint << "in" << QStringList(qtTranslationPaths());
|
||||
break;
|
||||
case TranslationLoadResult::NoDirectory:
|
||||
qCWarning(TranslationLoaderLog)
|
||||
<< "No Qt translations directory exists, looked in" << QStringList(qtTranslationPaths());
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TranslationLoadResult TranslationLoader::loadApplication(QTranslator &_translator,
|
||||
const QString &_prefix,
|
||||
const QString &_lang,
|
||||
const QList<QString> &_directories)
|
||||
{
|
||||
if (_lang.isEmpty()) {
|
||||
return TranslationLoadResult::Missing;
|
||||
}
|
||||
|
||||
const QString nameHint = _prefix + QLatin1Char('_') + _lang;
|
||||
const auto result = loadFrom(_translator, nameHint, _directories);
|
||||
|
||||
switch (result) {
|
||||
case TranslationLoadResult::Loaded:
|
||||
qCInfo(TranslationLoaderLog) << "Loaded" << _prefix << "translation" << nameHint << "at"
|
||||
<< _translator.filePath();
|
||||
break;
|
||||
case TranslationLoadResult::Missing:
|
||||
qCDebug(TranslationLoaderLog)
|
||||
<< "No" << _prefix << "translation" << nameHint << "in" << QStringList(_directories);
|
||||
break;
|
||||
case TranslationLoadResult::NoDirectory:
|
||||
qCWarning(TranslationLoaderLog)
|
||||
<< "No translation directory exists, looked in" << QStringList(_directories);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#ifndef TRANSLATION_LOADER_H
|
||||
#define TRANSLATION_LOADER_H
|
||||
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
|
||||
class QTranslator;
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(TranslationLoaderLog)
|
||||
|
||||
/** @brief The outcome of a translation load attempt. */
|
||||
enum class TranslationLoadResult
|
||||
{
|
||||
Loaded, /**< A translation file was found and loaded into the translator. */
|
||||
Missing, /**< The search directories exist but hold no file for this language. */
|
||||
NoDirectory /**< Not a single search directory exists, so no lookup could be attempted. */
|
||||
};
|
||||
|
||||
namespace TranslationLoader
|
||||
{
|
||||
/**
|
||||
* @brief Every directory that may hold Qt's own translations, in order of preference.
|
||||
*
|
||||
* More than one entry is needed because a relocated Qt (Flatpak, AppImage, a bundled SDK) can report
|
||||
* a prefix-relative translations path that does not exist: the KDE runtime reports /usr/translations
|
||||
* while its qt_*.qm files live in /usr/share/qt6/translations.
|
||||
*/
|
||||
QList<QString> qtTranslationCandidates();
|
||||
|
||||
/**
|
||||
* @brief The candidates that actually exist, most likely first, so no caller searches a path that is
|
||||
* not there.
|
||||
*/
|
||||
QList<QString> qtTranslationPaths();
|
||||
|
||||
/**
|
||||
* @brief The directories to search for an application's own translation files, in order of preference.
|
||||
*
|
||||
* @param _installedPath The location the platform installs the translation files in, derived from the
|
||||
* executable's path.
|
||||
* @return The installed location, followed by the executable's own directory: a build tree keeps the
|
||||
* .qm files next to the binary instead of in the installed layout, so a developer build would
|
||||
* otherwise find no translations at all.
|
||||
*/
|
||||
QList<QString> applicationTranslationPaths(const QString &_installedPath);
|
||||
|
||||
/**
|
||||
* @brief Loads the first _nameHint + language translation file found in _directories into _translator.
|
||||
*
|
||||
* QTranslator discards whatever it held before every load, so each translation file needs its own
|
||||
* translator; loading a second file into the first one silently drops the first.
|
||||
*
|
||||
* @param _translator The translator to load into.
|
||||
* @param _nameHint The translation file name without its language and suffix, e.g. "qt_de" or "cockatrice_de".
|
||||
* @param _directories The directories to search, in order of preference.
|
||||
* @return What the search found.
|
||||
*/
|
||||
TranslationLoadResult loadFrom(QTranslator &_translator, const QString &_nameHint, const QList<QString> &_directories);
|
||||
|
||||
/**
|
||||
* @brief Loads Qt's own translation for _lang, which covers the strings of Qt's own dialogs.
|
||||
*
|
||||
* @param _translator The translator to load into; must not also be used for an application translation.
|
||||
* @param _lang The language code, e.g. "de". An empty language loads nothing.
|
||||
* @return What the search found.
|
||||
*/
|
||||
TranslationLoadResult loadQt(QTranslator &_translator, const QString &_lang);
|
||||
|
||||
/**
|
||||
* @brief The language codes of the <_prefix>_<lang>.qm files in _directories, sorted by code.
|
||||
*
|
||||
* Lets a language selector offer exactly the languages that can be loaded.
|
||||
*/
|
||||
QList<QString> availableLanguages(const QString &_prefix, const QList<QString> &_directories);
|
||||
|
||||
/**
|
||||
* @brief Loads the <_prefix>_<_lang>.qm translation file from _directories into _translator.
|
||||
*
|
||||
* @param _translator The translator to load into; must not also be used for Qt's own translation.
|
||||
* @param _prefix The translation file prefix, e.g. "cockatrice" or "oracle".
|
||||
* @param _lang The language code, e.g. "de". An empty language loads nothing.
|
||||
* @param _directories The directories to search, in order of preference.
|
||||
* @return What the search found.
|
||||
*/
|
||||
TranslationLoadResult loadApplication(QTranslator &_translator,
|
||||
const QString &_prefix,
|
||||
const QString &_lang,
|
||||
const QList<QString> &_directories);
|
||||
} // namespace TranslationLoader
|
||||
|
||||
#endif // TRANSLATION_LOADER_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue