mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-28 00:42:19 -07:00
[Client] Show localized card names, texts and pictures
Localization wiring now runs end to end: the oracle importer collects
foreignData for the configured language and the client renders it.
- [Oracle] Import localized names and rules texts for the selected cardLang
- single-face cards store their foreignData name and full text
- multi-face (split/adventure/aftermath/prepare) cards collect the joined
name once and join each face's translated text with the same separator
as the English merge; an incomplete translation falls back to English;
the joined text follows the same highest-priority-set policy as the
single-face path and is only collected when localization is enabled
- the wizard switching languages re-imports the card database
- [Client] Display localized card info throughout the client
- card info text/picture widgets and the game board re-render on language
change
- pictures resolve cardLang art through Scryfall's named endpoint using the
localized name, falling back to id-based art when no match exists
- deck editor keeps canonical English names as card identity (EditRole)
while showing localized names (DisplayRole), so decks and wire names
stay stable
- [Card] Add CardLocalization-backed name/text lookup and cards.xml v4
localization elements with a bounded-size translation cache
- [Tests] Cover oracle foreignData import (incl. multi-face joins, priority
and fallback paths), XML v4 localization parsing, deck model localized
display and the language-aware settings default
Existing installations need to re-run Oracle to see translations: localized
data only lands in cards.xml when the Oracle app is started with the
preferred language selected — launch the separate "Oracle" program that
ships with Cockatrice, pick the language in the wizard and let it re-import
the card database.
The client's database cache (cards.xml.cache) is invalidated by the cache
format bump and the source-hash checks, but a cache written before the
re-import can still hold English-only entries (the hash uses file size and
mtime, so a same-size/same-timestamp rewrite may be served as-is); delete
cards.xml.cache and relaunch if no localized names/texts show up after
re-importing.
This commit is contained in:
parent
fd82b140a8
commit
8bb2337117
35 changed files with 1138 additions and 25 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
#include "abstract_card_item.h"
|
#include "abstract_card_item.h"
|
||||||
|
|
||||||
#include "../../client/settings/cache_settings.h"
|
#include "../../client/settings/cache_settings.h"
|
||||||
|
#include "../../interface/card_localization.h"
|
||||||
#include "../../interface/card_picture_loader/card_picture_loader.h"
|
#include "../../interface/card_picture_loader/card_picture_loader.h"
|
||||||
#include "../game_scene.h"
|
#include "../game_scene.h"
|
||||||
#include "../z_values.h"
|
#include "../z_values.h"
|
||||||
|
|
@ -26,6 +27,8 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef
|
||||||
|
|
||||||
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::displayCardNamesChanged, this,
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::displayCardNamesChanged, this,
|
||||||
[this] { update(); });
|
[this] { update(); });
|
||||||
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
|
||||||
|
[this] { update(); });
|
||||||
refreshCardInfo();
|
refreshCardInfo();
|
||||||
|
|
||||||
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this,
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this,
|
||||||
|
|
@ -171,7 +174,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
|
||||||
if (SettingsCache::instance().debug().getShowCardId()) {
|
if (SettingsCache::instance().debug().getShowCardId()) {
|
||||||
prefix = "#" + QString::number(id) + " ";
|
prefix = "#" + QString::number(id) + " ";
|
||||||
}
|
}
|
||||||
nameStr = prefix + cardRef.name;
|
nameStr = prefix + LocalizedCard::displayName(getCardInfo());
|
||||||
}
|
}
|
||||||
painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor,
|
painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor,
|
||||||
translatedSize.height() - 6 * scaleFactor),
|
translatedSize.height() - 6 * scaleFactor),
|
||||||
|
|
|
||||||
59
cockatrice/src/interface/card_localization.h
Normal file
59
cockatrice/src/interface/card_localization.h
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#ifndef COCKATRICE_CARD_LOCALIZATION_H
|
||||||
|
#define COCKATRICE_CARD_LOCALIZATION_H
|
||||||
|
|
||||||
|
#include "../client/settings/cache_settings.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <libcockatrice/card/card_info.h>
|
||||||
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
|
|
||||||
|
namespace LocalizedCard
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief The language code selected for localized card text and images.
|
||||||
|
*/
|
||||||
|
inline QString displayLang()
|
||||||
|
{
|
||||||
|
return SettingsCache::instance().cardsDisplay().getCardLang();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Card name in the configured display language, falling back to English.
|
||||||
|
* @param card The card to display.
|
||||||
|
* @return The localized name, or an empty string for a null card.
|
||||||
|
*/
|
||||||
|
inline QString displayName(const CardInfoPtr &card)
|
||||||
|
{
|
||||||
|
return card.isNull() ? QString() : card->getLocalizedName(displayLang());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Card rules text in the configured display language, falling back to English.
|
||||||
|
* @param card The card to display.
|
||||||
|
* @return The localized text, or an empty string for a null card.
|
||||||
|
*/
|
||||||
|
inline QString displayText(const CardInfoPtr &card)
|
||||||
|
{
|
||||||
|
return card.isNull() ? QString() : card->getLocalizedText(displayLang());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Card name in the configured display language, falling back to English.
|
||||||
|
* @param card The card to display.
|
||||||
|
*/
|
||||||
|
inline QString displayName(const CardInfo &card)
|
||||||
|
{
|
||||||
|
return card.getLocalizedName(displayLang());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Card rules text in the configured display language, falling back to English.
|
||||||
|
* @param card The card to display.
|
||||||
|
*/
|
||||||
|
inline QString displayText(const CardInfo &card)
|
||||||
|
{
|
||||||
|
return card.getLocalizedText(displayLang());
|
||||||
|
}
|
||||||
|
} // namespace LocalizedCard
|
||||||
|
|
||||||
|
#endif // COCKATRICE_CARD_LOCALIZATION_H
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <libcockatrice/settings/cache_storage_settings.h>
|
#include <libcockatrice/settings/cache_storage_settings.h>
|
||||||
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
#include <libcockatrice/settings/download_settings.h>
|
#include <libcockatrice/settings/download_settings.h>
|
||||||
#include <libcockatrice/settings/paths_settings.h>
|
#include <libcockatrice/settings/paths_settings.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
@ -37,6 +38,8 @@ CardPictureLoader::CardPictureLoader() : QObject(nullptr)
|
||||||
&CardPictureLoader::picsPathChanged);
|
&CardPictureLoader::picsPathChanged);
|
||||||
connect(&SettingsCache::instance().downloads(), &DownloadSettings::picDownloadChanged, this,
|
connect(&SettingsCache::instance().downloads(), &DownloadSettings::picDownloadChanged, this,
|
||||||
&CardPictureLoader::picDownloadChanged);
|
&CardPictureLoader::picDownloadChanged);
|
||||||
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
|
||||||
|
&CardPictureLoader::cardLangChanged);
|
||||||
|
|
||||||
qRegisterMetaType<ExactCard>();
|
qRegisterMetaType<ExactCard>();
|
||||||
connect(worker, &CardPictureLoaderWorker::imageLoaded, this, &CardPictureLoader::imageLoaded);
|
connect(worker, &CardPictureLoaderWorker::imageLoaded, this, &CardPictureLoader::imageLoaded);
|
||||||
|
|
@ -327,6 +330,15 @@ void CardPictureLoader::picsPathChanged()
|
||||||
QPixmapCache::clear();
|
QPixmapCache::clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CardPictureLoader::cardLangChanged()
|
||||||
|
{
|
||||||
|
// Localized images are fetched via a different URL, but the in-memory
|
||||||
|
// pixmap cache is keyed by card name/uuid, so drop everything cached
|
||||||
|
// (including failure timestamps) to force a reload in the new language.
|
||||||
|
QPixmapCache::clear();
|
||||||
|
failedAt.clear();
|
||||||
|
}
|
||||||
|
|
||||||
bool CardPictureLoader::hasCustomArt()
|
bool CardPictureLoader::hasCustomArt()
|
||||||
{
|
{
|
||||||
auto picsPath = SettingsCache::instance().paths().getPicsPath();
|
auto picsPath = SettingsCache::instance().paths().getPicsPath();
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,12 @@ private slots:
|
||||||
* Clears the QPixmap cache to reload images.
|
* Clears the QPixmap cache to reload images.
|
||||||
*/
|
*/
|
||||||
void picsPathChanged();
|
void picsPathChanged();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Triggered when the card language setting changes.
|
||||||
|
* Clears the in-memory picture caches so images reload in the new language.
|
||||||
|
*/
|
||||||
|
void cardLangChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,20 @@ void CardPictureToLoad::populateSetUrls()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const QString &urlTemplate : urlTemplates) {
|
QStringList orderedTemplates = urlTemplates;
|
||||||
|
if (SettingsCache::instance().cardsDisplay().getCardLang() != "en") {
|
||||||
|
// Scryfall serves localized art from per-language printings, which have
|
||||||
|
// their own ids. The ids stored in the card database belong to the
|
||||||
|
// English prints, so id-based templates (cards.scryfall.io,
|
||||||
|
// api.scryfall.com/cards/!set:uuid!) can only ever resolve English
|
||||||
|
// scans; the `lang=` parameter is ignored on them. Resolve the localized
|
||||||
|
// printing by its translated name (and the language code) ahead of the
|
||||||
|
// id-based templates instead.
|
||||||
|
orderedTemplates.prepend("https://api.scryfall.com/cards/named?fuzzy=!localizedName!&lang=!sflang!"
|
||||||
|
"&format=image&face=!prop:side!");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &urlTemplate : orderedTemplates) {
|
||||||
QString transformedUrl = transformUrl(urlTemplate);
|
QString transformedUrl = transformUrl(urlTemplate);
|
||||||
|
|
||||||
if (!transformedUrl.isEmpty()) {
|
if (!transformedUrl.isEmpty()) {
|
||||||
|
|
@ -282,8 +295,15 @@ QString CardPictureToLoad::transformUrl(const QString &urlTemplate) const
|
||||||
}
|
}
|
||||||
|
|
||||||
// language setting
|
// language setting
|
||||||
transformMap["!sflang!"] = QString(QCoreApplication::translate(
|
const QString cardLang = SettingsCache::instance().cardsDisplay().getCardLang();
|
||||||
"PictureLoader", "en", "code for scryfall's language property, not available for all languages"));
|
transformMap["!sflang!"] = cardLang;
|
||||||
|
|
||||||
|
// The localized printing's own id is unknown, so Scryfall must resolve it by
|
||||||
|
// its translated name (see populateSetUrls); expose that name for the
|
||||||
|
// `/cards/named` template.
|
||||||
|
if (cardLang != "en") {
|
||||||
|
transformMap["!localizedName!"] = card.getInfo().getLocalizedName(cardLang);
|
||||||
|
}
|
||||||
|
|
||||||
QString transformedUrl = urlTemplate;
|
QString transformedUrl = urlTemplate;
|
||||||
for (const QString &prop : transformMap.keys()) {
|
for (const QString &prop : transformMap.keys()) {
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,8 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT
|
||||||
|
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
|
||||||
|
&CardInfoPictureWidget::updatePixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "card_info_text_widget.h"
|
#include "card_info_text_widget.h"
|
||||||
|
|
||||||
#include "../../../game_graphics/board/card_item.h"
|
#include "../../../game_graphics/board/card_item.h"
|
||||||
|
#include "../../card_localization.h"
|
||||||
|
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
|
@ -39,6 +40,12 @@ CardInfoTextWidget::CardInfoTextWidget(QWidget *parent) : QFrame(parent), info(n
|
||||||
grid->setRowStretch(1, 1);
|
grid->setRowStretch(1, 1);
|
||||||
|
|
||||||
retranslateUi();
|
retranslateUi();
|
||||||
|
|
||||||
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, [this] {
|
||||||
|
if (currentCard) {
|
||||||
|
setCard(currentCard);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardInfoTextWidget::setTexts(const QString &propsText, const QString &textText)
|
void CardInfoTextWidget::setTexts(const QString &propsText, const QString &textText)
|
||||||
|
|
@ -60,7 +67,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard)
|
||||||
|
|
||||||
QString text = "<table width=\"100%\" border=0 cellspacing=0 cellpadding=0>";
|
QString text = "<table width=\"100%\" border=0 cellspacing=0 cellpadding=0>";
|
||||||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
|
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
|
||||||
.arg(tr("Name:"), card->getName().toHtmlEscaped());
|
.arg(tr("Name:"), LocalizedCard::displayName(card).toHtmlEscaped());
|
||||||
|
|
||||||
if (!exactCard.getPrinting().isEmpty()) {
|
if (!exactCard.getPrinting().isEmpty()) {
|
||||||
QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped();
|
QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped();
|
||||||
|
|
@ -94,7 +101,8 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard)
|
||||||
}
|
}
|
||||||
|
|
||||||
text += "</table>";
|
text += "</table>";
|
||||||
setTexts(text, card->getText());
|
setTexts(text, LocalizedCard::displayText(card));
|
||||||
|
currentCard = exactCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardInfoTextWidget::setInvalidCardName(const QString &cardName)
|
void CardInfoTextWidget::setInvalidCardName(const QString &cardName)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ private:
|
||||||
QScrollArea *propsScroll;
|
QScrollArea *propsScroll;
|
||||||
QTextEdit *textLabel;
|
QTextEdit *textLabel;
|
||||||
CardInfoPtr info;
|
CardInfoPtr info;
|
||||||
|
ExactCard currentCard; ///< Last card set, re-rendered when the card language changes.
|
||||||
void setTexts(const QString &propsText, const QString &textText);
|
void setTexts(const QString &propsText, const QString &textText);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -345,7 +345,9 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard()
|
||||||
if (!current.isValid()) {
|
if (!current.isValid()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
// The display role holds the localized card name; the edit role always carries the
|
||||||
|
// canonical English name needed to look the card up in the database.
|
||||||
|
const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
||||||
const QModelIndex gparent = current.parent().parent();
|
const QModelIndex gparent = current.parent().parent();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,20 @@
|
||||||
#include "deck_state_manager.h"
|
#include "deck_state_manager.h"
|
||||||
|
|
||||||
|
#include "../../../client/settings/cache_settings.h"
|
||||||
|
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
#include <libcockatrice/deck_list/deck_list_history_manager.h>
|
#include <libcockatrice/deck_list/deck_list_history_manager.h>
|
||||||
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
|
#include <libcockatrice/deck_list/tree/inner_deck_list_node.h>
|
||||||
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
|
|
||||||
DeckStateManager::DeckStateManager(QObject *parent)
|
DeckStateManager::DeckStateManager(QObject *parent)
|
||||||
: QObject(parent), deckList(QSharedPointer<DeckList>(new DeckList)),
|
: QObject(parent), deckList(QSharedPointer<DeckList>(new DeckList)),
|
||||||
deckListModel(new DeckListModel(this, deckList)), historyManager(new DeckListHistoryManager(this))
|
deckListModel(new DeckListModel(this, deckList)), historyManager(new DeckListHistoryManager(this))
|
||||||
{
|
{
|
||||||
|
deckListModel->setDisplayLanguage(SettingsCache::instance().cardsDisplay().getCardLang());
|
||||||
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, deckListModel,
|
||||||
|
[this](const QString &lang) { deckListModel->setDisplayLanguage(lang); });
|
||||||
|
|
||||||
connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, [this] {
|
connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, [this] {
|
||||||
setModified(true);
|
setModified(true);
|
||||||
emit historyChanged();
|
emit historyChanged();
|
||||||
|
|
@ -260,7 +267,10 @@ bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
// The display role holds the localized card name; the edit role always carries the
|
||||||
|
// canonical English name needed to look the card up in the database.
|
||||||
|
QString displayCardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
||||||
|
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString();
|
||||||
QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString();
|
||||||
QModelIndex gparent = idx.parent().parent();
|
QModelIndex gparent = idx.parent().parent();
|
||||||
|
|
||||||
|
|
@ -277,7 +287,7 @@ bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx)
|
||||||
|
|
||||||
QString reason = tr("Moved to %1 1 × \"%2\" (%3)") //
|
QString reason = tr("Moved to %1 1 × \"%2\" (%3)") //
|
||||||
.arg(otherZoneName)
|
.arg(otherZoneName)
|
||||||
.arg(cardName)
|
.arg(displayCardName)
|
||||||
.arg(providerId);
|
.arg(providerId);
|
||||||
|
|
||||||
return modifyDeck(reason, [&idx, &cardName, &providerId, &otherZoneName](auto model) {
|
return modifyDeck(reason, [&idx, &cardName, &providerId, &otherZoneName](auto model) {
|
||||||
|
|
@ -291,9 +301,8 @@ bool DeckStateManager::removeCardAtIndex(const QModelIndex &idx)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString();
|
QString reason =
|
||||||
|
tr("Removed \"%1\" (all copies)").arg(idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString());
|
||||||
QString reason = tr("Removed \"%1\" (all copies)").arg(cardName);
|
|
||||||
|
|
||||||
return modifyDeck(reason, [&idx](auto model) { return model->removeRow(idx.row(), idx.parent()); });
|
return modifyDeck(reason, [&idx](auto model) { return model->removeRow(idx.row(), idx.parent()); });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -416,6 +416,11 @@ void DlgSettings::setTab(int index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AbstractSettingsPage *DlgSettings::page(SettingsPage which) const
|
||||||
|
{
|
||||||
|
return pages.value(static_cast<int>(which));
|
||||||
|
}
|
||||||
|
|
||||||
void DlgSettings::updateLanguage()
|
void DlgSettings::updateLanguage()
|
||||||
{
|
{
|
||||||
qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ public:
|
||||||
|
|
||||||
explicit DlgSettings(QWidget *parent = nullptr);
|
explicit DlgSettings(QWidget *parent = nullptr);
|
||||||
void setTab(int index);
|
void setTab(int index);
|
||||||
|
AbstractSettingsPage *page(SettingsPage which) const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onTabClicked(int index);
|
void onTabClicked(int index);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
#include "general_settings_page.h"
|
#include "general_settings_page.h"
|
||||||
|
|
||||||
#include "../../../client/settings/cache_settings.h"
|
#include "../../../client/settings/cache_settings.h"
|
||||||
|
#include "../interface/card_picture_loader/card_picture_loader.h"
|
||||||
#include "../main.h"
|
#include "../main.h"
|
||||||
#include "../server/user/user_info_connection.h"
|
#include "../server/user/user_info_connection.h"
|
||||||
#include "update/client/release_channel.h"
|
#include "update/client/release_channel.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QFile>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
#include <libcockatrice/card/card_localization.h>
|
||||||
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
#include <libcockatrice/settings/paths_settings.h>
|
#include <libcockatrice/settings/paths_settings.h>
|
||||||
#include <libcockatrice/settings/personal_settings.h>
|
#include <libcockatrice/settings/personal_settings.h>
|
||||||
#include <libcockatrice/settings/tabs_settings.h>
|
#include <libcockatrice/settings/tabs_settings.h>
|
||||||
|
|
@ -46,10 +51,27 @@ GeneralSettingsPage::GeneralSettingsPage()
|
||||||
connect(&languageBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
connect(&languageBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
&GeneralSettingsPage::languageBoxChanged);
|
&GeneralSettingsPage::languageBoxChanged);
|
||||||
|
|
||||||
|
// card text & images language, independent of the UI language
|
||||||
|
cardLanguageBox.addItem(tr("English"), "en");
|
||||||
|
for (const QString &code : CardLocalization::supportedLanguages()) {
|
||||||
|
cardLanguageBox.addItem(CardLocalization::languageDisplayName(code), code);
|
||||||
|
}
|
||||||
|
const int cardLangIndex = cardLanguageBox.findData(SettingsCache::instance().cardsDisplay().getCardLang());
|
||||||
|
cardLanguageBox.setCurrentIndex(cardLangIndex < 0 ? 0 : cardLangIndex);
|
||||||
|
|
||||||
|
connect(&cardLanguageBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
|
&GeneralSettingsPage::cardLanguageBoxChanged);
|
||||||
|
|
||||||
auto *languageGrid = new QGridLayout;
|
auto *languageGrid = new QGridLayout;
|
||||||
languageGrid->addWidget(&languageLabel, 0, 0);
|
languageGrid->addWidget(&languageLabel, 0, 0);
|
||||||
languageGrid->addWidget(&languageBox, 0, 1);
|
languageGrid->addWidget(&languageBox, 0, 1);
|
||||||
languageGrid->addWidget(&advertiseTranslationPageLabel, 1, 1, Qt::AlignRight);
|
languageGrid->addWidget(&cardLanguageLabel, 1, 0);
|
||||||
|
languageGrid->addWidget(&cardLanguageBox, 1, 1);
|
||||||
|
languageGrid->addWidget(&cardLanguageNoteLabel, 2, 1);
|
||||||
|
languageGrid->addWidget(&advertiseTranslationPageLabel, 3, 1, Qt::AlignRight);
|
||||||
|
|
||||||
|
cardLanguageNoteLabel.setWordWrap(true);
|
||||||
|
cardLanguageNoteLabel.setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
|
|
||||||
languageGroupBox = new QGroupBox;
|
languageGroupBox = new QGroupBox;
|
||||||
languageGroupBox->setLayout(languageGrid);
|
languageGroupBox->setLayout(languageGrid);
|
||||||
|
|
@ -412,6 +434,43 @@ void GeneralSettingsPage::languageBoxChanged(int index)
|
||||||
SettingsCache::instance().personal().setLang(languageBox.itemData(index).toString());
|
SettingsCache::instance().personal().setLang(languageBox.itemData(index).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GeneralSettingsPage::cardLanguageBoxChanged(int index)
|
||||||
|
{
|
||||||
|
const QString lang = cardLanguageBox.itemData(index).toString();
|
||||||
|
SettingsCache::instance().cardsDisplay().setCardLang(lang);
|
||||||
|
|
||||||
|
// Switching to a non-default language only takes effect after the card
|
||||||
|
// database is re-imported with that language selected; English data is always
|
||||||
|
// present, so switching back to English needs no prompt.
|
||||||
|
if (lang == "en") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The binary cache does not track the language its entries were imported in,
|
||||||
|
// and the downloaded pictures were fetched with English art names, so both are
|
||||||
|
// stale until Oracle re-imports the database in the new language: drop them.
|
||||||
|
QFile::remove(SettingsCache::instance().getCardDatabasePath() + ".cache");
|
||||||
|
CardPictureLoader::clearNetworkCache();
|
||||||
|
CardPictureLoader::clearPixmapCache();
|
||||||
|
|
||||||
|
const QMessageBox::StandardButton answer =
|
||||||
|
QMessageBox::question(this, tr("Card text & images language changed"),
|
||||||
|
tr("<p>The card database only contains English card data. To see cards in <b>%1</b>, "
|
||||||
|
"<b>Oracle</b> must run once with this language selected and re-import the card "
|
||||||
|
"database.</p>"
|
||||||
|
"<p>The cached database and the downloaded card pictures have been cleared, so a "
|
||||||
|
"re-import is picked up without stale entries.</p>"
|
||||||
|
"<p>Run Oracle now?</p>")
|
||||||
|
.arg(cardLanguageBox.itemText(index)),
|
||||||
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||||
|
|
||||||
|
// The answer only controls whether Oracle starts right away; the caches stay
|
||||||
|
// cleared so the next import or launch rebuilds them in the new language.
|
||||||
|
if (answer == QMessageBox::Yes) {
|
||||||
|
emit cardDatabaseUpdateRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GeneralSettingsPage::updateStartupServerControlsVisibility()
|
void GeneralSettingsPage::updateStartupServerControlsVisibility()
|
||||||
{
|
{
|
||||||
const int index = startupTabSelector.currentIndex();
|
const int index = startupTabSelector.currentIndex();
|
||||||
|
|
@ -429,6 +488,10 @@ void GeneralSettingsPage::retranslateUi()
|
||||||
|
|
||||||
languageGroupBox->setTitle(tr("Language settings"));
|
languageGroupBox->setTitle(tr("Language settings"));
|
||||||
languageLabel.setText(tr("Language:"));
|
languageLabel.setText(tr("Language:"));
|
||||||
|
cardLanguageBox.setItemText(0, tr("English"));
|
||||||
|
cardLanguageLabel.setText(tr("Card text & images language:"));
|
||||||
|
cardLanguageNoteLabel.setText(
|
||||||
|
tr("Foreign card names, text and art apply after you update the card database (Oracle)."));
|
||||||
advertiseTranslationPageLabel.setText(
|
advertiseTranslationPageLabel.setText(
|
||||||
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
|
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ public:
|
||||||
static QStringList findQmFiles();
|
static QStringList findQmFiles();
|
||||||
static QString languageName(const QString &lang);
|
static QString languageName(const QString &lang);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/// Request to re-import the card database with the newly selected card language
|
||||||
|
void cardDatabaseUpdateRequested();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void deckPathButtonClicked();
|
void deckPathButtonClicked();
|
||||||
void filtersPathButtonClicked();
|
void filtersPathButtonClicked();
|
||||||
|
|
@ -33,6 +37,7 @@ private slots:
|
||||||
void tokenDatabasePathButtonClicked();
|
void tokenDatabasePathButtonClicked();
|
||||||
void resetAllPathsClicked();
|
void resetAllPathsClicked();
|
||||||
void languageBoxChanged(int index);
|
void languageBoxChanged(int index);
|
||||||
|
void cardLanguageBoxChanged(int index);
|
||||||
void updateStartupServerControlsVisibility();
|
void updateStartupServerControlsVisibility();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -46,6 +51,10 @@ private:
|
||||||
QComboBox languageBox;
|
QComboBox languageBox;
|
||||||
QLabel advertiseTranslationPageLabel;
|
QLabel advertiseTranslationPageLabel;
|
||||||
|
|
||||||
|
QLabel cardLanguageLabel;
|
||||||
|
QComboBox cardLanguageBox;
|
||||||
|
QLabel cardLanguageNoteLabel;
|
||||||
|
|
||||||
QLabel updateReleaseChannelLabel;
|
QLabel updateReleaseChannelLabel;
|
||||||
QComboBox updateReleaseChannelBox;
|
QComboBox updateReleaseChannelBox;
|
||||||
QCheckBox startupUpdateCheckCheckBox;
|
QCheckBox startupUpdateCheckCheckBox;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "archidekt_api_response_deck_display_widget.h"
|
#include "archidekt_api_response_deck_display_widget.h"
|
||||||
|
|
||||||
|
#include "../../../../../../client/settings/cache_settings.h"
|
||||||
#include "../../../../../deck_loader/card_node_function.h"
|
#include "../../../../../deck_loader/card_node_function.h"
|
||||||
#include "../../../../../deck_loader/deck_loader.h"
|
#include "../../../../../deck_loader/deck_loader.h"
|
||||||
#include "../../../../cards/card_size_widget.h"
|
#include "../../../../cards/card_size_widget.h"
|
||||||
|
|
@ -10,6 +11,7 @@
|
||||||
|
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <libcockatrice/card/import/card_name_normalizer.h>
|
#include <libcockatrice/card/import/card_name_normalizer.h>
|
||||||
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
|
|
||||||
ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWidget *parent,
|
ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWidget *parent,
|
||||||
ArchidektApiResponseDeck _response,
|
ArchidektApiResponseDeck _response,
|
||||||
|
|
@ -120,6 +122,9 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
|
||||||
}
|
}
|
||||||
|
|
||||||
model = new DeckListModel(this);
|
model = new DeckListModel(this);
|
||||||
|
model->setDisplayLanguage(SettingsCache::instance().cardsDisplay().getCardLang());
|
||||||
|
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, model,
|
||||||
|
[this](const QString &lang) { model->setDisplayLanguage(lang); });
|
||||||
connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset);
|
connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset);
|
||||||
|
|
||||||
auto decklist = QSharedPointer<DeckList>(new DeckList);
|
auto decklist = QSharedPointer<DeckList>(new DeckList);
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
#include "../interface/widgets/dialogs/dlg_update.h"
|
#include "../interface/widgets/dialogs/dlg_update.h"
|
||||||
#include "../interface/widgets/dialogs/dlg_view_log.h"
|
#include "../interface/widgets/dialogs/dlg_view_log.h"
|
||||||
#include "../interface/widgets/onboarding/first_run_wizard.h"
|
#include "../interface/widgets/onboarding/first_run_wizard.h"
|
||||||
|
#include "../interface/widgets/settings_page/general_settings_page.h"
|
||||||
#include "../interface/widgets/tabs/tab_game.h"
|
#include "../interface/widgets/tabs/tab_game.h"
|
||||||
#include "../interface/widgets/tabs/tab_server.h"
|
#include "../interface/widgets/tabs/tab_server.h"
|
||||||
#include "../interface/widgets/tabs/tab_supervisor.h"
|
#include "../interface/widgets/tabs/tab_supervisor.h"
|
||||||
|
|
@ -246,6 +247,8 @@ void MainWindow::actFullScreen(bool checked)
|
||||||
void MainWindow::actSettings()
|
void MainWindow::actSettings()
|
||||||
{
|
{
|
||||||
DlgSettings dlg(this);
|
DlgSettings dlg(this);
|
||||||
|
auto *generalPage = qobject_cast<GeneralSettingsPage *>(dlg.page(DlgSettings::GeneralPage));
|
||||||
|
connect(generalPage, &GeneralSettingsPage::cardDatabaseUpdateRequested, this, &MainWindow::actCheckCardUpdates);
|
||||||
dlg.exec();
|
dlg.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,13 @@
|
||||||
</xs:extension>
|
</xs:extension>
|
||||||
</xs:simpleContent>
|
</xs:simpleContent>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
<xs:complexType name="localizationType">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element type="xs:string" name="name" minOccurs="0" maxOccurs="1" />
|
||||||
|
<xs:element type="xs:string" name="text" minOccurs="0" maxOccurs="1" />
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:string" name="lang" use="required" />
|
||||||
|
</xs:complexType>
|
||||||
<xs:group name="cardPropertyGroup">
|
<xs:group name="cardPropertyGroup">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:any processContents="skip" minOccurs="0" maxOccurs="unbounded" />
|
<xs:any processContents="skip" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
|
@ -49,6 +56,13 @@
|
||||||
<xs:group ref="cardPropertyGroup"/>
|
<xs:group ref="cardPropertyGroup"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
|
<xs:element name="localizations" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element type="localizationType" name="localization" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
<xs:element type="cardInSetType" name="set" minOccurs="1" maxOccurs="unbounded" />
|
<xs:element type="cardInSetType" name="set" minOccurs="1" maxOccurs="unbounded" />
|
||||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
<xs:element type="relatedType" name="related" minOccurs="0" maxOccurs="unbounded" />
|
<xs:element type="relatedType" name="related" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ set(CMAKE_AUTORCC ON)
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
libcockatrice/card/card_info.h
|
libcockatrice/card/card_info.h
|
||||||
libcockatrice/card/card_info_comparator.h
|
libcockatrice/card/card_info_comparator.h
|
||||||
|
libcockatrice/card/card_localization.h
|
||||||
libcockatrice/card/lazy_properties_hash.h
|
libcockatrice/card/lazy_properties_hash.h
|
||||||
libcockatrice/card/database/card_database.h
|
libcockatrice/card/database/card_database.h
|
||||||
libcockatrice/card/database/card_database_loader.h
|
libcockatrice/card/database/card_database_loader.h
|
||||||
|
|
@ -27,6 +28,7 @@ add_library(
|
||||||
${MOC_SOURCES}
|
${MOC_SOURCES}
|
||||||
libcockatrice/card/card_info.cpp
|
libcockatrice/card/card_info.cpp
|
||||||
libcockatrice/card/card_info_comparator.cpp
|
libcockatrice/card/card_info_comparator.cpp
|
||||||
|
libcockatrice/card/card_localization.cpp
|
||||||
libcockatrice/card/lazy_properties_hash.cpp
|
libcockatrice/card/lazy_properties_hash.cpp
|
||||||
libcockatrice/card/database/card_database.cpp
|
libcockatrice/card/database/card_database.cpp
|
||||||
libcockatrice/card/database/card_database_cache.cpp
|
libcockatrice/card/database/card_database_cache.cpp
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,9 @@ private:
|
||||||
QString text; ///< Text description or rules text of the card.
|
QString text; ///< Text description or rules text of the card.
|
||||||
bool isToken; ///< Whether this card is a token or not.
|
bool isToken; ///< Whether this card is a token or not.
|
||||||
|
|
||||||
|
QMap<QString, QString> localizedNames; ///< Localized card names, keyed by language code.
|
||||||
|
QMap<QString, QString> localizedTexts; ///< Localized rules text, keyed by language code.
|
||||||
|
|
||||||
LazyPropertiesHash properties; ///< Key-value store of dynamic card properties.
|
LazyPropertiesHash properties; ///< Key-value store of dynamic card properties.
|
||||||
|
|
||||||
QList<CardRelation *> relatedCards; ///< Forward references to related cards.
|
QList<CardRelation *> relatedCards; ///< Forward references to related cards.
|
||||||
|
|
@ -151,7 +154,8 @@ public:
|
||||||
*/
|
*/
|
||||||
CardInfo(const CardInfo &other)
|
CardInfo(const CardInfo &other)
|
||||||
: QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text),
|
: QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text),
|
||||||
isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
|
isToken(other.isToken), localizedNames(other.localizedNames), localizedTexts(other.localizedTexts),
|
||||||
|
properties(other.properties), relatedCards(other.relatedCards),
|
||||||
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
|
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
|
||||||
setsToPrintings(other.setsToPrintings), uiAttributes(other.uiAttributes), setsNames(other.setsNames),
|
setsToPrintings(other.setsToPrintings), uiAttributes(other.uiAttributes), setsNames(other.setsNames),
|
||||||
altNames(other.altNames)
|
altNames(other.altNames)
|
||||||
|
|
@ -270,6 +274,96 @@ public:
|
||||||
text = _text;
|
text = _text;
|
||||||
emit cardInfoChanged(smartThis);
|
emit cardInfoChanged(smartThis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the card name in the given language, falling back to the
|
||||||
|
* English name when no localization is available.
|
||||||
|
*
|
||||||
|
* @param lang Language code (e.g. "de", "ja", "zhs").
|
||||||
|
* @return The localized name, or the English name as fallback.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const QString &getLocalizedName(const QString &lang) const
|
||||||
|
{
|
||||||
|
const auto it = localizedNames.constFind(lang);
|
||||||
|
return it != localizedNames.constEnd() ? it.value() : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the rules text in the given language, falling back to the
|
||||||
|
* English text when no localization is available.
|
||||||
|
*
|
||||||
|
* @param lang Language code (e.g. "de", "ja", "zhs").
|
||||||
|
* @return The localized text, or the English text as fallback.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const QString &getLocalizedText(const QString &lang) const
|
||||||
|
{
|
||||||
|
const auto it = localizedTexts.constFind(lang);
|
||||||
|
return it != localizedTexts.constEnd() ? it.value() : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the localized card names keyed by language code.
|
||||||
|
*
|
||||||
|
* Only languages that have an entry are present; there is no English
|
||||||
|
* fallback in this map.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const QMap<QString, QString> &getLocalizedNames() const
|
||||||
|
{
|
||||||
|
return localizedNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the localized rules text keyed by language code.
|
||||||
|
*
|
||||||
|
* Only languages that have an entry are present; there is no English
|
||||||
|
* fallback in this map.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const QMap<QString, QString> &getLocalizedTexts() const
|
||||||
|
{
|
||||||
|
return localizedTexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the card name for the given language.
|
||||||
|
*
|
||||||
|
* @param lang Language code.
|
||||||
|
* @param _localizedName The localized card name.
|
||||||
|
*/
|
||||||
|
void setLocalizedName(const QString &lang, const QString &_localizedName)
|
||||||
|
{
|
||||||
|
if (localizedNames.value(lang) == _localizedName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localizedNames.insert(lang, _localizedName);
|
||||||
|
emit cardInfoChanged(smartThis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the rules text for the given language.
|
||||||
|
*
|
||||||
|
* @param lang Language code.
|
||||||
|
* @param _localizedText The localized rules text.
|
||||||
|
*/
|
||||||
|
void setLocalizedText(const QString &lang, const QString &_localizedText)
|
||||||
|
{
|
||||||
|
if (localizedTexts.value(lang) == _localizedText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localizedTexts.insert(lang, _localizedText);
|
||||||
|
emit cardInfoChanged(smartThis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the language codes for which this card has a localized
|
||||||
|
* name or rules text.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QStringList localizationLanguages() const
|
||||||
|
{
|
||||||
|
QStringList languages = localizedNames.keys();
|
||||||
|
languages.append(localizedTexts.keys());
|
||||||
|
languages.removeDuplicates();
|
||||||
|
return languages;
|
||||||
|
}
|
||||||
[[nodiscard]] bool getIsToken() const
|
[[nodiscard]] bool getIsToken() const
|
||||||
{
|
{
|
||||||
return isToken;
|
return isToken;
|
||||||
|
|
|
||||||
25
libcockatrice_card/libcockatrice/card/card_localization.cpp
Normal file
25
libcockatrice_card/libcockatrice/card/card_localization.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include "card_localization.h"
|
||||||
|
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
namespace CardLocalization
|
||||||
|
{
|
||||||
|
const QStringList &supportedLanguages()
|
||||||
|
{
|
||||||
|
static const QStringList languages = {"cs", "de", "es", "fr", "it", "ja", "ko", "pt", "ru", "zhs", "zht", "he"};
|
||||||
|
return languages;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString languageDisplayName(const QString &lang)
|
||||||
|
{
|
||||||
|
if (lang == "zhs") {
|
||||||
|
return QStringLiteral("简体中文");
|
||||||
|
}
|
||||||
|
if (lang == "zht") {
|
||||||
|
return QStringLiteral("繁體中文");
|
||||||
|
}
|
||||||
|
const QString nativeName = QLocale(lang).nativeLanguageName();
|
||||||
|
return nativeName.isEmpty() ? lang : nativeName;
|
||||||
|
}
|
||||||
|
} // namespace CardLocalization
|
||||||
40
libcockatrice_card/libcockatrice/card/card_localization.h
Normal file
40
libcockatrice_card/libcockatrice/card/card_localization.h
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#ifndef CARD_LOCALIZATION_H
|
||||||
|
#define CARD_LOCALIZATION_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace CardLocalization
|
||||||
|
* @ingroup Cards
|
||||||
|
*
|
||||||
|
* @brief Shared language metadata for localized card text and images.
|
||||||
|
*
|
||||||
|
* Lists the language codes Cockatrice can display localized card data for and
|
||||||
|
* provides human-readable names. The list is shared between Oracle (which
|
||||||
|
* imports the selected language's card data) and the client settings UI (which
|
||||||
|
* offers the language choice).
|
||||||
|
*/
|
||||||
|
namespace CardLocalization
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Language codes for which localized card data can be imported/displayed.
|
||||||
|
*
|
||||||
|
* Matches the languages Scryfall can serve localized card images for. "en" is
|
||||||
|
* always available as the default/fallback and is not listed here.
|
||||||
|
*
|
||||||
|
* @return The list of supported language codes.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const QStringList &supportedLanguages();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Human-readable native name for a language code.
|
||||||
|
*
|
||||||
|
* @param lang Language code (e.g. "de", "ja", "zhs").
|
||||||
|
* @return The language's name in its own language, or the code itself if
|
||||||
|
* it cannot be resolved.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString languageDisplayName(const QString &lang);
|
||||||
|
} // namespace CardLocalization
|
||||||
|
|
||||||
|
#endif // CARD_LOCALIZATION_H
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr quint32 CACHE_MAGIC = 0x43445243; // "CDRC"
|
constexpr quint32 CACHE_MAGIC = 0x43445243; // "CDRC"
|
||||||
constexpr quint32 CACHE_VERSION = 2;
|
constexpr quint32 CACHE_VERSION = 3;
|
||||||
|
|
||||||
// ---- Primitives -----------------------------------------------------------
|
// ---- Primitives -----------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -71,6 +71,35 @@ QDate readDate(QDataStream &in)
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void writeStringMap(QDataStream &out, const QMap<QString, QString> &map)
|
||||||
|
{
|
||||||
|
out << static_cast<quint32>(map.size());
|
||||||
|
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
|
||||||
|
writeString(out, it.key());
|
||||||
|
writeString(out, it.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QString, QString> readStringMap(QDataStream &in)
|
||||||
|
{
|
||||||
|
QMap<QString, QString> map;
|
||||||
|
quint32 count = 0;
|
||||||
|
in >> count;
|
||||||
|
if (in.status() != QDataStream::Ok) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
for (quint32 i = 0; i < count; ++i) {
|
||||||
|
QString key = readString(in);
|
||||||
|
QString value = readString(in);
|
||||||
|
if (in.status() != QDataStream::Ok) {
|
||||||
|
map.clear();
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
map.insert(key, value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- CardRelation ----------------------------------------------------------
|
// ---- CardRelation ----------------------------------------------------------
|
||||||
|
|
||||||
void writeRelation(QDataStream &out, const CardRelation *rel)
|
void writeRelation(QDataStream &out, const CardRelation *rel)
|
||||||
|
|
@ -195,6 +224,10 @@ void writeCard(QDataStream &out, const CardInfoPtr &card)
|
||||||
for (const CardRelation *rel : reverse) {
|
for (const CardRelation *rel : reverse) {
|
||||||
writeRelation(out, rel);
|
writeRelation(out, rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// localized card data
|
||||||
|
writeStringMap(out, card->getLocalizedNames());
|
||||||
|
writeStringMap(out, card->getLocalizedTexts());
|
||||||
}
|
}
|
||||||
|
|
||||||
CardInfoPtr readCard(QDataStream &in, const SetNameMap &sets)
|
CardInfoPtr readCard(QDataStream &in, const SetNameMap &sets)
|
||||||
|
|
@ -268,8 +301,25 @@ CardInfoPtr readCard(QDataStream &in, const SetNameMap &sets)
|
||||||
reverse.append(readRelation(in));
|
reverse.append(readRelation(in));
|
||||||
}
|
}
|
||||||
|
|
||||||
return CardInfo::newInstance(name, text, isToken, propertiesBlob, related, reverse, cardSets, ui, simpleName,
|
CardInfoPtr card = CardInfo::newInstance(name, text, isToken, propertiesBlob, related, reverse, cardSets, ui,
|
||||||
altNames, false);
|
simpleName, altNames, false);
|
||||||
|
|
||||||
|
const QMap<QString, QString> localizedNames = readStringMap(in);
|
||||||
|
if (in.status() != QDataStream::Ok) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const QMap<QString, QString> localizedTexts = readStringMap(in);
|
||||||
|
if (in.status() != QDataStream::Ok) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
for (auto it = localizedNames.constBegin(); it != localizedNames.constEnd(); ++it) {
|
||||||
|
card->setLocalizedName(it.key(), it.value());
|
||||||
|
}
|
||||||
|
for (auto it = localizedTexts.constBegin(); it != localizedTexts.constEnd(); ++it) {
|
||||||
|
card->setLocalizedText(it.key(), it.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- FormatRules -----------------------------------------------------------
|
// ---- FormatRules -----------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -273,6 +273,8 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||||
QString name = QString("");
|
QString name = QString("");
|
||||||
QString text = QString("");
|
QString text = QString("");
|
||||||
QHash<QString, QString> properties;
|
QHash<QString, QString> properties;
|
||||||
|
QMap<QString, QString> localizedNames;
|
||||||
|
QMap<QString, QString> localizedTexts;
|
||||||
QList<CardRelation *> relatedCards, reverseRelatedCards;
|
QList<CardRelation *> relatedCards, reverseRelatedCards;
|
||||||
auto _sets = SetToPrintingsMap();
|
auto _sets = SetToPrintingsMap();
|
||||||
int tableRow = 0;
|
int tableRow = 0;
|
||||||
|
|
@ -298,6 +300,44 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||||
// generic properties
|
// generic properties
|
||||||
} else if (xmlName == "prop") {
|
} else if (xmlName == "prop") {
|
||||||
properties = loadCardPropertiesFromXml(xml);
|
properties = loadCardPropertiesFromXml(xml);
|
||||||
|
// localized card data
|
||||||
|
} else if (xmlName == "localizations") {
|
||||||
|
while (!xml.atEnd()) {
|
||||||
|
if (xml.readNextStartElement()) {
|
||||||
|
const QString elementName = xml.name().toString();
|
||||||
|
if (elementName == "localization") {
|
||||||
|
const QString lang = xml.attributes().value("lang").toString();
|
||||||
|
QString localizedName;
|
||||||
|
QString localizedText;
|
||||||
|
while (!xml.atEnd()) {
|
||||||
|
if (xml.readNext() == QXmlStreamReader::EndElement) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (xml.isStartElement()) {
|
||||||
|
const QString childName = xml.name().toString();
|
||||||
|
QString value = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||||
|
if (childName == "name") {
|
||||||
|
localizedName = value;
|
||||||
|
} else if (childName == "text") {
|
||||||
|
localizedText = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!lang.isEmpty()) {
|
||||||
|
if (!localizedName.isEmpty()) {
|
||||||
|
localizedNames.insert(lang, localizedName);
|
||||||
|
}
|
||||||
|
if (!localizedText.isEmpty()) {
|
||||||
|
localizedTexts.insert(lang, localizedText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
xml.skipCurrentElement();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
// positioning info
|
// positioning info
|
||||||
} else if (xmlName == "tablerow") {
|
} else if (xmlName == "tablerow") {
|
||||||
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
|
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
|
||||||
|
|
@ -401,6 +441,12 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||||
.upsideDownArt = upsideDown};
|
.upsideDownArt = upsideDown};
|
||||||
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
|
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
|
||||||
reverseRelatedCards, _sets, attributes);
|
reverseRelatedCards, _sets, attributes);
|
||||||
|
for (auto it = localizedNames.constBegin(); it != localizedNames.constEnd(); ++it) {
|
||||||
|
newCard->setLocalizedName(it.key(), it.value());
|
||||||
|
}
|
||||||
|
for (auto it = localizedTexts.constBegin(); it != localizedTexts.constEnd(); ++it) {
|
||||||
|
newCard->setLocalizedText(it.key(), it.value());
|
||||||
|
}
|
||||||
if (targetData) {
|
if (targetData) {
|
||||||
// Mirror CardDatabase::addCard: if a card with this name already
|
// Mirror CardDatabase::addCard: if a card with this name already
|
||||||
// exists, merge the new printings into it instead of replacing.
|
// exists, merge the new printings into it instead of replacing.
|
||||||
|
|
@ -517,6 +563,26 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
|
||||||
}
|
}
|
||||||
xml.writeEndElement();
|
xml.writeEndElement();
|
||||||
|
|
||||||
|
// localized card data
|
||||||
|
const QStringList localizedLanguages = info->localizationLanguages();
|
||||||
|
if (!localizedLanguages.isEmpty()) {
|
||||||
|
xml.writeStartElement("localizations");
|
||||||
|
const QMap<QString, QString> &localizedNames = info->getLocalizedNames();
|
||||||
|
const QMap<QString, QString> &localizedTexts = info->getLocalizedTexts();
|
||||||
|
for (const QString &lang : localizedLanguages) {
|
||||||
|
xml.writeStartElement("localization");
|
||||||
|
xml.writeAttribute("lang", lang);
|
||||||
|
if (localizedNames.contains(lang)) {
|
||||||
|
xml.writeTextElement("name", localizedNames.value(lang));
|
||||||
|
}
|
||||||
|
if (localizedTexts.contains(lang)) {
|
||||||
|
xml.writeTextElement("text", localizedTexts.value(lang));
|
||||||
|
}
|
||||||
|
xml.writeEndElement();
|
||||||
|
}
|
||||||
|
xml.writeEndElement();
|
||||||
|
}
|
||||||
|
|
||||||
// sets
|
// sets
|
||||||
for (const auto &printings : info->getSets()) {
|
for (const auto &printings : info->getSets()) {
|
||||||
for (const PrintingInfo &set : printings) {
|
for (const PrintingInfo &set : printings) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H
|
#ifndef COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H
|
||||||
#define COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H
|
#define COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
class ICardsDisplaySettingsProvider
|
class ICardsDisplaySettingsProvider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -26,6 +28,7 @@ public:
|
||||||
[[nodiscard]] virtual int getEDHRecCardSize() const = 0;
|
[[nodiscard]] virtual int getEDHRecCardSize() const = 0;
|
||||||
[[nodiscard]] virtual int getArchidektPreviewSize() const = 0;
|
[[nodiscard]] virtual int getArchidektPreviewSize() const = 0;
|
||||||
[[nodiscard]] virtual int getSampleHandSize() const = 0;
|
[[nodiscard]] virtual int getSampleHandSize() const = 0;
|
||||||
|
[[nodiscard]] virtual QString getCardLang() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H
|
#endif // COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,16 @@ DeckListModel::~DeckListModel()
|
||||||
delete root;
|
delete root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeckListModel::setDisplayLanguage(const QString &lang)
|
||||||
|
{
|
||||||
|
if (displayLang == lang) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
displayLang = lang;
|
||||||
|
emit layoutAboutToBeChanged();
|
||||||
|
emit layoutChanged();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Extract the value from the card that is used for the group criteria.
|
* @brief Extract the value from the card that is used for the group criteria.
|
||||||
* @param info Pointer to card information.
|
* @param info Pointer to card information.
|
||||||
|
|
@ -181,8 +191,15 @@ QVariant DeckListModel::data(const QModelIndex &index, int role) const
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case DeckListModelColumns::CARD_AMOUNT:
|
case DeckListModelColumns::CARD_AMOUNT:
|
||||||
return card->getNumber();
|
return card->getNumber();
|
||||||
case DeckListModelColumns::CARD_NAME:
|
case DeckListModelColumns::CARD_NAME: {
|
||||||
|
if (role == Qt::DisplayRole) {
|
||||||
|
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(card->getName());
|
||||||
|
if (info) {
|
||||||
|
return info->getLocalizedName(displayLang);
|
||||||
|
}
|
||||||
|
}
|
||||||
return card->getName();
|
return card->getName();
|
||||||
|
}
|
||||||
case DeckListModelColumns::CARD_SET:
|
case DeckListModelColumns::CARD_SET:
|
||||||
return card->getCardSetShortName();
|
return card->getCardSetShortName();
|
||||||
case DeckListModelColumns::CARD_COLLECTOR_NUMBER:
|
case DeckListModelColumns::CARD_COLLECTOR_NUMBER:
|
||||||
|
|
|
||||||
|
|
@ -287,6 +287,16 @@ public:
|
||||||
explicit DeckListModel(QObject *parent, const QSharedPointer<DeckList> &deckList);
|
explicit DeckListModel(QObject *parent, const QSharedPointer<DeckList> &deckList);
|
||||||
~DeckListModel() override;
|
~DeckListModel() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Selects the language code for localized card names in the display role.
|
||||||
|
*
|
||||||
|
* The model never reads global settings itself; callers wire this to the card
|
||||||
|
* language setting (including reacting to its changes) rather than the model
|
||||||
|
* querying it.
|
||||||
|
* @param lang Language code; "en" shows the canonical English names.
|
||||||
|
*/
|
||||||
|
void setDisplayLanguage(const QString &lang);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the root index of the model.
|
* @brief Returns the root index of the model.
|
||||||
* @return QModelIndex representing the root node.
|
* @return QModelIndex representing the root node.
|
||||||
|
|
@ -408,6 +418,7 @@ private:
|
||||||
DeckListModelGroupCriteria::Type activeGroupCriteria = DeckListModelGroupCriteria::MAIN_TYPE;
|
DeckListModelGroupCriteria::Type activeGroupCriteria = DeckListModelGroupCriteria::MAIN_TYPE;
|
||||||
int lastKnownColumn; /**< Last column used for sorting. */
|
int lastKnownColumn; /**< Last column used for sorting. */
|
||||||
Qt::SortOrder lastKnownOrder; /**< Last known sort order. */
|
Qt::SortOrder lastKnownOrder; /**< Last known sort order. */
|
||||||
|
QString displayLang = "en"; /**< Language code for localized card names in the display role. */
|
||||||
|
|
||||||
InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent);
|
InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent);
|
||||||
QModelIndex nodeToIndex(AbstractDecklistNode *node) const;
|
QModelIndex nodeToIndex(AbstractDecklistNode *node) const;
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,11 @@ int CardsDisplaySettings::getSampleHandSize() const
|
||||||
return getValue("sampleHandSize", "cards", "cardSize", 7).toInt();
|
return getValue("sampleHandSize", "cards", "cardSize", 7).toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString CardsDisplaySettings::getCardLang() const
|
||||||
|
{
|
||||||
|
return getValue("cardLang", QString(), QString(), "en").toString();
|
||||||
|
}
|
||||||
|
|
||||||
void CardsDisplaySettings::setDisplayCardNames(bool _displayCardNames)
|
void CardsDisplaySettings::setDisplayCardNames(bool _displayCardNames)
|
||||||
{
|
{
|
||||||
setValue(_displayCardNames, "displayCardNames");
|
setValue(_displayCardNames, "displayCardNames");
|
||||||
|
|
@ -224,3 +229,16 @@ void CardsDisplaySettings::setSampleHandSize(int _sampleHandSize)
|
||||||
setValue(_sampleHandSize, "sampleHandSize", "cards", "cardSize");
|
setValue(_sampleHandSize, "sampleHandSize", "cards", "cardSize");
|
||||||
emit sampleHandSizeChanged(_sampleHandSize);
|
emit sampleHandSizeChanged(_sampleHandSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CardsDisplaySettings::setCardLang(const QString &_cardLang)
|
||||||
|
{
|
||||||
|
if (_cardLang == getCardLang()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setValue(_cardLang, "cardLang");
|
||||||
|
// Flush to disk immediately: the Oracle tool is a separate process that
|
||||||
|
// reads this value to decide which foreignData to import, so it must not
|
||||||
|
// observe a stale (pre-change) value.
|
||||||
|
sync();
|
||||||
|
emit cardLangChanged(_cardLang);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ public:
|
||||||
[[nodiscard]] int getEDHRecCardSize() const override;
|
[[nodiscard]] int getEDHRecCardSize() const override;
|
||||||
[[nodiscard]] int getArchidektPreviewSize() const override;
|
[[nodiscard]] int getArchidektPreviewSize() const override;
|
||||||
[[nodiscard]] int getSampleHandSize() const override;
|
[[nodiscard]] int getSampleHandSize() const override;
|
||||||
|
[[nodiscard]] QString getCardLang() const override;
|
||||||
|
|
||||||
void setDisplayCardNames(bool _displayCardNames);
|
void setDisplayCardNames(bool _displayCardNames);
|
||||||
void setRoundCardCorners(bool _roundCardCorners);
|
void setRoundCardCorners(bool _roundCardCorners);
|
||||||
|
|
@ -52,6 +53,7 @@ public:
|
||||||
void setEDHRecCardSize(int _edhrecCardSize);
|
void setEDHRecCardSize(int _edhrecCardSize);
|
||||||
void setArchidektPreviewCardSize(int _archidektPreviewCardSize);
|
void setArchidektPreviewCardSize(int _archidektPreviewCardSize);
|
||||||
void setSampleHandSize(int _sampleHandSize);
|
void setSampleHandSize(int _sampleHandSize);
|
||||||
|
void setCardLang(const QString &_cardLang);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void displayCardNamesChanged();
|
void displayCardNamesChanged();
|
||||||
|
|
@ -68,6 +70,7 @@ signals:
|
||||||
void edhRecCardSizeChanged();
|
void edhRecCardSizeChanged();
|
||||||
void archidektPreviewSizeChanged();
|
void archidektPreviewSizeChanged();
|
||||||
void sampleHandSizeChanged(int amount);
|
void sampleHandSizeChanged(int amount);
|
||||||
|
void cardLangChanged(const QString &lang);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CardsDisplaySettings(const QString &settingPath, QObject *parent = nullptr);
|
explicit CardsDisplaySettings(const QString &settingPath, QObject *parent = nullptr);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
const QStringList DownloadSettings::DEFAULT_DOWNLOAD_URLS = {
|
const QStringList DownloadSettings::DEFAULT_DOWNLOAD_URLS = {
|
||||||
"https://cards.scryfall.io/large/!prop:side!/!set:uuid_substr_0_1!/!set:uuid_substr_1_1!/!set:uuid!.jpg",
|
"https://cards.scryfall.io/large/!prop:side!/!set:uuid_substr_0_1!/!set:uuid_substr_1_1!/!set:uuid!.jpg",
|
||||||
"https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!",
|
"https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!&lang=!sflang!",
|
||||||
"https://api.scryfall.com/cards/multiverse/!set:muid!?format=image",
|
"https://api.scryfall.com/cards/multiverse/!set:muid!?format=image&lang=!sflang!",
|
||||||
"https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card",
|
"https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card",
|
||||||
"https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"};
|
"https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
#include <libcockatrice/card/card_localization.h>
|
||||||
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
|
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
|
||||||
#include <libcockatrice/card/relation/card_relation.h>
|
#include <libcockatrice/card/relation/card_relation.h>
|
||||||
|
|
||||||
|
|
@ -22,8 +23,9 @@ static const QList<AllowedCount> kSingletonCounts = {{1, "legal"}, {0, "banned"}
|
||||||
SplitCardPart::SplitCardPart(const QString &_name,
|
SplitCardPart::SplitCardPart(const QString &_name,
|
||||||
const QString &_text,
|
const QString &_text,
|
||||||
const QHash<QString, QString> &_properties,
|
const QHash<QString, QString> &_properties,
|
||||||
const PrintingInfo &_printingInfo)
|
const PrintingInfo &_printingInfo,
|
||||||
: name(_name), text(_text), properties(_properties), printingInfo(_printingInfo)
|
const QString &_localizedText)
|
||||||
|
: name(_name), text(_text), localizedText(_localizedText), properties(_properties), printingInfo(_printingInfo)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,6 +35,42 @@ OracleImporter::OracleImporter(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OracleImporter::setCardLang(const QString &lang)
|
||||||
|
{
|
||||||
|
cardLang = lang.trimmed().toLower();
|
||||||
|
localizationEnabled = cardLang != "en" && CardLocalization::supportedLanguages().contains(cardLang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Maps the MTGJSON foreignData language names to the short codes used by
|
||||||
|
* Scryfall and stored in cards.xml (e.g. "German" -> "de").
|
||||||
|
* @param language The language name found in the MTGJSON foreignData entries.
|
||||||
|
* @return The short language code, or an empty string if unknown.
|
||||||
|
*/
|
||||||
|
static QString mtgjsonLanguageToCode(const QString &language)
|
||||||
|
{
|
||||||
|
static const QHash<QString, QString> map = {
|
||||||
|
{"Chinese Simplified", "zhs"},
|
||||||
|
{"Chinese Traditional", "zht"},
|
||||||
|
{"English", "en"},
|
||||||
|
{"French", "fr"},
|
||||||
|
{"German", "de"},
|
||||||
|
{"Greek", "grc"},
|
||||||
|
{"Ancient Greek", "grc"},
|
||||||
|
{"Hebrew", "he"},
|
||||||
|
{"Italian", "it"},
|
||||||
|
{"Japanese", "ja"},
|
||||||
|
{"Korean", "ko"},
|
||||||
|
{"Latin", "la"},
|
||||||
|
{"Phyrexian", "ph"},
|
||||||
|
{"Portuguese (Brazil)", "pt"},
|
||||||
|
{"Russian", "ru"},
|
||||||
|
{"Sanskrit", "sa"},
|
||||||
|
{"Spanish", "es"},
|
||||||
|
};
|
||||||
|
return map.value(language);
|
||||||
|
}
|
||||||
|
|
||||||
static CardSet::Priority getSetPriority(const QString &setType, const QString &shortName)
|
static CardSet::Priority getSetPriority(const QString &setType, const QString &shortName)
|
||||||
{
|
{
|
||||||
if (!setTypePriorities.contains(setType.toLower())) {
|
if (!setTypePriorities.contains(setType.toLower())) {
|
||||||
|
|
@ -254,6 +292,101 @@ static QString getJsonString(const QJsonObject &obj, const QString &key)
|
||||||
return obj.value(key).toVariant().toString();
|
return obj.value(key).toVariant().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QString normalizeCardName(QString name)
|
||||||
|
{
|
||||||
|
// Mirror of the name cleanup applied in addCard(), so collected localization
|
||||||
|
// keys line up with the card map keys (Æ → AE, curly apostrophe → straight).
|
||||||
|
name = name.replace("Æ", "AE");
|
||||||
|
name = name.replace("’", "'");
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString matchingForeignEntryText(const QJsonObject &card, const QString &cardLang)
|
||||||
|
{
|
||||||
|
// Multi-face cards (split/aftermath/adventure/prepare) expose each face as a
|
||||||
|
// separate card object, each with its own foreignData entry carrying that
|
||||||
|
// face's rules text; single-face cards carry the full text in one entry.
|
||||||
|
const QJsonArray foreignData = card.value("foreignData").toArray();
|
||||||
|
for (const QJsonValue &entryValue : foreignData) {
|
||||||
|
const QJsonObject entry = entryValue.toObject();
|
||||||
|
// MTGJSON reports languages by long-form name ("German"); match the
|
||||||
|
// short code ("de") that Scryfall and cards.xml use.
|
||||||
|
if (mtgjsonLanguageToCode(getJsonString(entry, "language")) == cardLang) {
|
||||||
|
return getJsonString(entry, "text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OracleImporter::collectForeignData(const QString &cardKey,
|
||||||
|
const CardSetPtr ¤tSet,
|
||||||
|
const QJsonObject &card,
|
||||||
|
bool collectText)
|
||||||
|
{
|
||||||
|
if (!localizationEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalizedCardEntry incoming;
|
||||||
|
bool found = false;
|
||||||
|
const QJsonArray foreignData = card.value("foreignData").toArray();
|
||||||
|
for (const QJsonValue &entryValue : foreignData) {
|
||||||
|
const QJsonObject entry = entryValue.toObject();
|
||||||
|
// MTGJSON reports languages by long-form name ("German"); match the
|
||||||
|
// short code ("de") that Scryfall and cards.xml use.
|
||||||
|
if (mtgjsonLanguageToCode(getJsonString(entry, "language")) != cardLang) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
incoming.name = getJsonString(entry, "name");
|
||||||
|
incoming.text = collectText ? getJsonString(entry, "text") : QString();
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer the entry from the highest-priority set (lower enum value = more
|
||||||
|
// authoritative); printings of equal priority keep the first one seen.
|
||||||
|
incoming.priority = currentSet->getPriority();
|
||||||
|
const auto existing = localizedEntries.constFind(cardKey);
|
||||||
|
if (existing == localizedEntries.constEnd() || incoming.priority < existing->priority) {
|
||||||
|
localizedEntries.insert(cardKey, incoming);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OracleImporter::applyLocalizedData()
|
||||||
|
{
|
||||||
|
if (!localizationEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto it = localizedEntries.constBegin(); it != localizedEntries.constEnd(); ++it) {
|
||||||
|
CardInfoPtr card = cards.value(it.key());
|
||||||
|
if (card.isNull()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const LocalizedCardEntry &entry = it.value();
|
||||||
|
if (!entry.name.isEmpty()) {
|
||||||
|
card->setLocalizedName(cardLang, entry.name);
|
||||||
|
}
|
||||||
|
if (!entry.text.isEmpty()) {
|
||||||
|
card->setLocalizedText(cardLang, entry.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localizedEntries.clear();
|
||||||
|
|
||||||
|
for (auto it = splitLocalizedTexts.constBegin(); it != splitLocalizedTexts.constEnd(); ++it) {
|
||||||
|
CardInfoPtr card = cards.value(it.key());
|
||||||
|
if (card.isNull()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!it.value().text.isEmpty()) {
|
||||||
|
card->setLocalizedText(cardLang, it.value().text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
splitLocalizedTexts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList)
|
int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList)
|
||||||
{
|
{
|
||||||
// mtgjson name => xml name
|
// mtgjson name => xml name
|
||||||
|
|
@ -397,13 +530,21 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
||||||
// split cards are considered a single card, enqueue for later merging
|
// split cards are considered a single card, enqueue for later merging
|
||||||
if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") {
|
if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") {
|
||||||
auto _faceName = getJsonString(card, "faceName");
|
auto _faceName = getJsonString(card, "faceName");
|
||||||
SplitCardPart split(_faceName, text, properties, printingInfo);
|
// MTGJSON exposes each face as a separate card object, each with its
|
||||||
|
// own foreignData entry holding that face's rules text; collect it so
|
||||||
|
// the per-face texts can be joined the same way as the English text.
|
||||||
|
const QString faceLocalizedText =
|
||||||
|
localizationEnabled ? matchingForeignEntryText(card, cardLang) : QString();
|
||||||
|
SplitCardPart split(_faceName, text, properties, printingInfo, faceLocalizedText);
|
||||||
auto found_iter = splitCards.find(name + numProperty);
|
auto found_iter = splitCards.find(name + numProperty);
|
||||||
if (found_iter == splitCards.end()) {
|
if (found_iter == splitCards.end()) {
|
||||||
splitCards.insert(name + numProperty, {{split}, name});
|
splitCards.insert(name + numProperty, {{split}, name});
|
||||||
} else {
|
} else {
|
||||||
found_iter->first.append(split);
|
found_iter->first.append(split);
|
||||||
}
|
}
|
||||||
|
// MTGJSON's foreignData name is the joined name present on every
|
||||||
|
// face, so collect the name once.
|
||||||
|
collectForeignData(normalizeCardName(name), currentSet, card, false);
|
||||||
} else {
|
} else {
|
||||||
// relations
|
// relations
|
||||||
QList<CardRelation *> relatedCards;
|
QList<CardRelation *> relatedCards;
|
||||||
|
|
@ -446,6 +587,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collectForeignData(normalizeCardName(name + numComponent), currentSet, card);
|
||||||
|
|
||||||
CardInfoPtr newCard =
|
CardInfoPtr newCard =
|
||||||
addCard(name + numComponent, text, isToken, std::move(properties), relatedCards, printingInfo);
|
addCard(name + numComponent, text, isToken, std::move(properties), relatedCards, printingInfo);
|
||||||
numCards++;
|
numCards++;
|
||||||
|
|
@ -459,6 +602,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
||||||
QList<QPair<QList<SplitCardPart>, QString>> partsAndNames = splitCards.values();
|
QList<QPair<QList<SplitCardPart>, QString>> partsAndNames = splitCards.values();
|
||||||
for (auto [splitCardParts, name] : partsAndNames) {
|
for (auto [splitCardParts, name] : partsAndNames) {
|
||||||
QString text;
|
QString text;
|
||||||
|
QString localizedText;
|
||||||
|
bool localizedTextComplete = true;
|
||||||
QHash<QString, QString> properties;
|
QHash<QString, QString> properties;
|
||||||
PrintingInfo printingInfo;
|
PrintingInfo printingInfo;
|
||||||
|
|
||||||
|
|
@ -468,6 +613,21 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
||||||
}
|
}
|
||||||
text.append(tmp.getText());
|
text.append(tmp.getText());
|
||||||
|
|
||||||
|
// Build the cardLang text by joining each face's translated text with
|
||||||
|
// the same separator as the English text. Any face missing a complete
|
||||||
|
// translation abandons the whole join, falling back to the English text.
|
||||||
|
if (localizedTextComplete) {
|
||||||
|
const QString partLocalizedText = tmp.getLocalizedText();
|
||||||
|
if (partLocalizedText.isEmpty()) {
|
||||||
|
localizedTextComplete = false;
|
||||||
|
} else {
|
||||||
|
if (!localizedText.isEmpty()) {
|
||||||
|
localizedText.append(splitCardTextSeparator);
|
||||||
|
}
|
||||||
|
localizedText.append(partLocalizedText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (properties.isEmpty()) {
|
if (properties.isEmpty()) {
|
||||||
properties = tmp.getProperties();
|
properties = tmp.getProperties();
|
||||||
printingInfo = tmp.getPrintingInfo();
|
printingInfo = tmp.getPrintingInfo();
|
||||||
|
|
@ -500,6 +660,18 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CardInfoPtr newCard = addCard(name, text, isToken, std::move(properties), {}, printingInfo);
|
CardInfoPtr newCard = addCard(name, text, isToken, std::move(properties), {}, printingInfo);
|
||||||
|
if (localizationEnabled && localizedTextComplete && !localizedText.isEmpty()) {
|
||||||
|
// Same priority policy as collectForeignData(): the joined text from the
|
||||||
|
// highest-priority set seen so far wins, applied once all printings are in.
|
||||||
|
LocalizedCardEntry entry;
|
||||||
|
entry.text = localizedText;
|
||||||
|
entry.priority = currentSet->getPriority();
|
||||||
|
const QString entryKey = normalizeCardName(name);
|
||||||
|
const auto existing = splitLocalizedTexts.constFind(entryKey);
|
||||||
|
if (existing == splitLocalizedTexts.constEnd() || entry.priority < existing->priority) {
|
||||||
|
splitLocalizedTexts.insert(entryKey, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
numCards++;
|
numCards++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -654,6 +826,8 @@ int OracleImporter::startImport()
|
||||||
emit setIndexChanged(numCardsInSet, setIndex, curSetToParse.getLongName());
|
emit setIndexChanged(numCardsInSet, setIndex, curSetToParse.getLongName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyLocalizedData();
|
||||||
|
|
||||||
emit setIndexChanged(0, setIndex, QString());
|
emit setIndexChanged(0, setIndex, QString());
|
||||||
|
|
||||||
// total number of sets
|
// total number of sets
|
||||||
|
|
@ -679,4 +853,6 @@ void OracleImporter::clear()
|
||||||
cards.clear();
|
cards.clear();
|
||||||
allSets.clear();
|
allSets.clear();
|
||||||
rawSetsData.clear();
|
rawSetsData.clear();
|
||||||
|
localizedEntries.clear();
|
||||||
|
splitLocalizedTexts.clear();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,8 @@ public:
|
||||||
SplitCardPart(const QString &_name,
|
SplitCardPart(const QString &_name,
|
||||||
const QString &_text,
|
const QString &_text,
|
||||||
const QHash<QString, QString> &_properties,
|
const QHash<QString, QString> &_properties,
|
||||||
const PrintingInfo &_printingInfo);
|
const PrintingInfo &_printingInfo,
|
||||||
|
const QString &_localizedText = QString());
|
||||||
inline const QString &getName() const
|
inline const QString &getName() const
|
||||||
{
|
{
|
||||||
return name;
|
return name;
|
||||||
|
|
@ -116,6 +117,13 @@ public:
|
||||||
{
|
{
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @brief The cardLang rules text of this face's foreignData entry, if any.
|
||||||
|
*/
|
||||||
|
inline const QString &getLocalizedText() const
|
||||||
|
{
|
||||||
|
return localizedText;
|
||||||
|
}
|
||||||
inline const QHash<QString, QString> &getProperties() const
|
inline const QHash<QString, QString> &getProperties() const
|
||||||
{
|
{
|
||||||
return properties;
|
return properties;
|
||||||
|
|
@ -128,10 +136,18 @@ public:
|
||||||
private:
|
private:
|
||||||
QString name;
|
QString name;
|
||||||
QString text;
|
QString text;
|
||||||
|
QString localizedText;
|
||||||
QHash<QString, QString> properties;
|
QHash<QString, QString> properties;
|
||||||
PrintingInfo printingInfo;
|
PrintingInfo printingInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LocalizedCardEntry
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QString text;
|
||||||
|
CardSet::Priority priority = CardSet::PriorityLowest;
|
||||||
|
};
|
||||||
|
|
||||||
class OracleImporter : public QObject
|
class OracleImporter : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
@ -171,12 +187,53 @@ private:
|
||||||
*/
|
*/
|
||||||
QAtomicInt importCancelled;
|
QAtomicInt importCancelled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ISO-639 language code whose foreignData is imported; "en" by default.
|
||||||
|
*/
|
||||||
|
QString cardLang = "en";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether cardLang is a supported language other than English, so per-card
|
||||||
|
* foreignData scanning can be skipped entirely when disabled.
|
||||||
|
*/
|
||||||
|
bool localizationEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Localized name/text collected per imported card key while parsing sets,
|
||||||
|
* applied to the CardInfo objects by applyLocalizedData() once all
|
||||||
|
* printings have been seen so the best-priority one wins.
|
||||||
|
*/
|
||||||
|
QMap<QString, LocalizedCardEntry> localizedEntries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cardLang rules text collected for split-card names while parsing sets,
|
||||||
|
* applied by applyLocalizedData(). Kept apart from localizedEntries because
|
||||||
|
* MTGJSON emits each split face as its own card object with the joined name
|
||||||
|
* on every foreignData entry: names and the per-face text join have different
|
||||||
|
* completeness and must not overwrite each other under the same key.
|
||||||
|
*/
|
||||||
|
QMap<QString, LocalizedCardEntry> splitLocalizedTexts;
|
||||||
|
|
||||||
CardInfoPtr addCard(QString name,
|
CardInfoPtr addCard(QString name,
|
||||||
const QString &text,
|
const QString &text,
|
||||||
bool isToken,
|
bool isToken,
|
||||||
QHash<QString, QString> properties,
|
QHash<QString, QString> properties,
|
||||||
const QList<CardRelation *> &relatedCards,
|
const QList<CardRelation *> &relatedCards,
|
||||||
const PrintingInfo &printingInfo);
|
const PrintingInfo &printingInfo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the first foreignData entry matching cardLang for the given card
|
||||||
|
* key, keeping the entry from the highest-priority set seen so far.
|
||||||
|
*
|
||||||
|
* Multi-face cards (split, adventure, aftermath, prepare) pass collectText =
|
||||||
|
* false: MTGJSON emits one foreignData entry per face with the same joined
|
||||||
|
* name but only that face's text, so the name is collected here while the
|
||||||
|
* per-face texts are joined during the split-card merge.
|
||||||
|
*/
|
||||||
|
void collectForeignData(const QString &cardKey,
|
||||||
|
const CardSetPtr ¤tSet,
|
||||||
|
const QJsonObject &card,
|
||||||
|
bool collectText = true);
|
||||||
signals:
|
signals:
|
||||||
void setIndexChanged(int cardsImported, int setIndex, const QString &setName);
|
void setIndexChanged(int cardsImported, int setIndex, const QString &setName);
|
||||||
void dataReadProgress(int bytesRead, int totalBytes);
|
void dataReadProgress(int bytesRead, int totalBytes);
|
||||||
|
|
@ -195,6 +252,15 @@ public:
|
||||||
{
|
{
|
||||||
progressReporting = enabled;
|
progressReporting = enabled;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Selects the ISO-639 language code whose foreignData is imported.
|
||||||
|
* English (the default) and unsupported codes disable localization.
|
||||||
|
*/
|
||||||
|
void setCardLang(const QString &lang);
|
||||||
|
const QString &getCardLang() const
|
||||||
|
{
|
||||||
|
return cardLang;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Scans the given JSON document for set metadata. Takes the data by value so
|
* Scans the given JSON document for set metadata. Takes the data by value so
|
||||||
* the wizard can hand over its decompressed buffer without copying it.
|
* the wizard can hand over its decompressed buffer without copying it.
|
||||||
|
|
@ -212,6 +278,12 @@ public:
|
||||||
{
|
{
|
||||||
importCancelled.storeRelease(1);
|
importCancelled.storeRelease(1);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Applies the collected localized names/texts to the imported cards.
|
||||||
|
* Called automatically at the end of startImport(); exposed separately so
|
||||||
|
* tests can drive it after importing sets directly.
|
||||||
|
*/
|
||||||
|
void applyLocalizedData();
|
||||||
bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion);
|
bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion);
|
||||||
int importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList);
|
int importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList);
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
#include <QtGui>
|
#include <QtGui>
|
||||||
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
#include <libcockatrice/settings/personal_settings.h>
|
#include <libcockatrice/settings/personal_settings.h>
|
||||||
|
|
||||||
OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
|
OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
|
||||||
|
|
@ -37,6 +38,9 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
|
||||||
connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &OracleWizard::updateLanguage);
|
connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &OracleWizard::updateLanguage);
|
||||||
|
|
||||||
importer = new OracleImporter(this);
|
importer = new OracleImporter(this);
|
||||||
|
// Import card text in the language the client displays, if supported:
|
||||||
|
// foreignData for any other language is never imported.
|
||||||
|
importer->setCardLang(SettingsCache::instance().cardsDisplay().getCardLang());
|
||||||
|
|
||||||
nam = new QNetworkAccessManager(this);
|
nam = new QNetworkAccessManager(this);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,14 @@
|
||||||
#include "test_card_database_path_provider.h"
|
#include "test_card_database_path_provider.h"
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <libcockatrice/card/card_info.h>
|
||||||
|
#include <libcockatrice/card/database/card_database_data.h>
|
||||||
|
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
|
||||||
|
#include <libcockatrice/card/lazy_properties_hash.h>
|
||||||
|
#include <libcockatrice/card/printing/printing_info.h>
|
||||||
#include <libcockatrice/interfaces/noop_card_preference_provider.h>
|
#include <libcockatrice/interfaces/noop_card_preference_provider.h>
|
||||||
#include <libcockatrice/interfaces/noop_card_set_priority_controller.h>
|
#include <libcockatrice/interfaces/noop_card_set_priority_controller.h>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -33,6 +38,49 @@ TEST(CardDatabaseTest, LoadXml)
|
||||||
ASSERT_EQ(0, db->query()->getAllMainCardTypes().size()) << "Types not empty after clear";
|
ASSERT_EQ(0, db->query()->getAllMainCardTypes().size()) << "Types not empty after clear";
|
||||||
ASSERT_EQ(NotLoaded, db->getLoadStatus()) << "Incorrect status after clear";
|
ASSERT_EQ(NotLoaded, db->getLoadStatus()) << "Incorrect status after clear";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(CardDatabaseTest, Xml4LocalizedDataRoundTrip)
|
||||||
|
{
|
||||||
|
NoopCardSetPriorityController controller;
|
||||||
|
CardSetPtr set =
|
||||||
|
CardSet::newInstance(&controller, "TST", "Test Set", "expansion", QDate(), CardSet::PriorityPrimary);
|
||||||
|
|
||||||
|
QHash<QString, QString> props;
|
||||||
|
props["manacost"] = "1R";
|
||||||
|
PrintingInfo printing(set, LazyPropertiesHash(props));
|
||||||
|
SetToPrintingsMap setsInfo;
|
||||||
|
setsInfo["TST"].append(printing);
|
||||||
|
|
||||||
|
CardInfo::UiAttributes attributes = {.tableRow = 1};
|
||||||
|
CardInfoPtr card =
|
||||||
|
CardInfo::newInstance("Lightning Bolt", "Deal 3 damage.", false, {}, {}, {}, setsInfo, attributes);
|
||||||
|
card->setLocalizedName("de", "Blitzschlag");
|
||||||
|
card->setLocalizedText("de", "Blitzschlag fügt 3 Schadenspunkte zu.");
|
||||||
|
|
||||||
|
SetNameMap sets;
|
||||||
|
sets.insert("TST", set);
|
||||||
|
CardNameMap cards;
|
||||||
|
cards.insert("Lightning Bolt", card);
|
||||||
|
|
||||||
|
QTemporaryDir tempDir;
|
||||||
|
const QString fileName = tempDir.filePath("cards.xml");
|
||||||
|
NoopCardPreferenceProvider prefProvider;
|
||||||
|
CockatriceXml4Parser writer(&prefProvider, &controller);
|
||||||
|
ASSERT_TRUE(writer.saveToFile({}, sets, cards, fileName));
|
||||||
|
|
||||||
|
CardDatabaseData data;
|
||||||
|
CockatriceXml4Parser parser(&prefProvider, &controller);
|
||||||
|
QFile file(fileName);
|
||||||
|
ASSERT_TRUE(file.open(QIODevice::ReadOnly));
|
||||||
|
parser.parseFileInto(file, data);
|
||||||
|
|
||||||
|
CardInfoPtr loaded = data.cards.value("Lightning Bolt");
|
||||||
|
ASSERT_FALSE(loaded.isNull());
|
||||||
|
ASSERT_EQ(loaded->getName(), "Lightning Bolt");
|
||||||
|
ASSERT_EQ(loaded->getLocalizedName("de"), "Blitzschlag");
|
||||||
|
ASSERT_EQ(loaded->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu.");
|
||||||
|
ASSERT_EQ(loaded->getLocalizedText("fr"), "Deal 3 damage.");
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,18 @@ protected:
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: build a single MTGJSON foreignData entry
|
||||||
|
QJsonObject makeForeignEntry(const QString &language, const QString &name, const QString &text)
|
||||||
|
{
|
||||||
|
QJsonObject entry;
|
||||||
|
entry["language"] = language;
|
||||||
|
entry["name"] = name;
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
entry["text"] = text;
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
NoopCardSetPriorityController *controller;
|
NoopCardSetPriorityController *controller;
|
||||||
OracleImporter *importer;
|
OracleImporter *importer;
|
||||||
CardSetPtr set;
|
CardSetPtr set;
|
||||||
|
|
@ -872,6 +884,243 @@ TEST_F(OracleImporterTest, DisablingProgressReportingSuppressesScanEmissions)
|
||||||
ASSERT_GT(emissions, 0);
|
ASSERT_GT(emissions, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Localized card text tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, ImportsLocalizedTextForRequestedLanguage)
|
||||||
|
{
|
||||||
|
QJsonObject card = makeCard("Lightning Bolt");
|
||||||
|
card["foreignData"] =
|
||||||
|
QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")};
|
||||||
|
QJsonArray cards{card};
|
||||||
|
|
||||||
|
importer->setCardLang("de");
|
||||||
|
importer->importCardsFromSet(set, cards);
|
||||||
|
importer->applyLocalizedData();
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Lightning Bolt");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
ASSERT_EQ(result->getLocalizedName("de"), "Blitzschlag");
|
||||||
|
ASSERT_EQ(result->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu.");
|
||||||
|
// English identity untouched
|
||||||
|
ASSERT_EQ(result->getName(), "Lightning Bolt");
|
||||||
|
ASSERT_EQ(result->getText(), "Rules text.");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, ImportsLocalizedNameAndTextForMultiFaceCards)
|
||||||
|
{
|
||||||
|
// MTGJSON reports multi-face cards (adventure/split/aftermath/prepare) as one
|
||||||
|
// card object per face; every face carries the joined name but only its own
|
||||||
|
// face's rules text in foreignData. The importer joins the per-face texts with
|
||||||
|
// the same separator as the English merge.
|
||||||
|
QJsonObject front = makeCard("Disruptive Stormbrood // Petty Revenge");
|
||||||
|
front["layout"] = "adventure";
|
||||||
|
front["faceName"] = "Disruptive Stormbrood";
|
||||||
|
front["side"] = "a";
|
||||||
|
front["foreignData"] = QJsonArray{
|
||||||
|
makeForeignEntry("German", "Disruptive Stormbrood // Kleinliche Rache",
|
||||||
|
"Fliegend\nWenn diese Kreatur ins Spiel kommt, zerstöre bis zu ein Artefakt oder eine "
|
||||||
|
"Verzauberung deiner Wahl.")};
|
||||||
|
QJsonObject back = makeCard("Disruptive Stormbrood // Petty Revenge");
|
||||||
|
back["layout"] = "adventure";
|
||||||
|
back["faceName"] = "Petty Revenge";
|
||||||
|
back["side"] = "b";
|
||||||
|
back["text"] = "Destroy target creature.";
|
||||||
|
back["foreignData"] = QJsonArray{makeForeignEntry("German", "Disruptive Stormbrood // Kleinliche Rache",
|
||||||
|
"Zerstöre eine Kreatur deiner Wahl mit Stärke 3 oder weniger.")};
|
||||||
|
QJsonArray cards{front, back};
|
||||||
|
|
||||||
|
importer->setCardLang("de");
|
||||||
|
importer->importCardsFromSet(set, cards);
|
||||||
|
importer->applyLocalizedData();
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Disruptive Stormbrood // Petty Revenge");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
ASSERT_EQ(result->getLocalizedName("de"), "Disruptive Stormbrood // Kleinliche Rache");
|
||||||
|
ASSERT_EQ(result->getLocalizedText("de"),
|
||||||
|
"Fliegend\nWenn diese Kreatur ins Spiel kommt, zerstöre bis zu ein Artefakt oder eine Verzauberung "
|
||||||
|
"deiner Wahl.\n\n---\n\nZerstöre eine Kreatur deiner Wahl mit Stärke 3 oder weniger.");
|
||||||
|
// English identity untouched
|
||||||
|
ASSERT_EQ(result->getName(), "Disruptive Stormbrood // Petty Revenge");
|
||||||
|
ASSERT_EQ(result->getText(), "Rules text.\n\n---\n\nDestroy target creature.");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, MultiFaceCardsWithoutCompleteForeignTextKeepEnglishText)
|
||||||
|
{
|
||||||
|
// Both faces must carry a foreignData text for the joined text; otherwise the
|
||||||
|
// rules text stays English while the localized name (from a later complete
|
||||||
|
// printing) is still applied.
|
||||||
|
QJsonObject front = makeCard("Wear // Tear");
|
||||||
|
front["layout"] = "split";
|
||||||
|
front["faceName"] = "Wear";
|
||||||
|
front["side"] = "a";
|
||||||
|
front["foreignData"] = QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Verschleiß-Text.")};
|
||||||
|
QJsonObject back = makeCard("Wear // Tear");
|
||||||
|
back["layout"] = "split";
|
||||||
|
back["faceName"] = "Tear";
|
||||||
|
back["side"] = "b";
|
||||||
|
back["text"] = "Tear rules text.";
|
||||||
|
back["foreignData"] = QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "")};
|
||||||
|
QJsonArray cards{front, back};
|
||||||
|
|
||||||
|
importer->setCardLang("de");
|
||||||
|
importer->importCardsFromSet(set, cards);
|
||||||
|
importer->applyLocalizedData();
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Wear // Tear");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
// The joined name is still applied.
|
||||||
|
ASSERT_EQ(result->getLocalizedName("de"), "Verschleiß // Zerrreißung");
|
||||||
|
// The incomplete text must not become the card's localized text.
|
||||||
|
ASSERT_TRUE(result->getLocalizedTexts().isEmpty());
|
||||||
|
ASSERT_EQ(result->getText(), "Rules text.\n\n---\n\nTear rules text.");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, DefaultLanguageSkipsForeignData)
|
||||||
|
{
|
||||||
|
QJsonObject card = makeCard("Lightning Bolt");
|
||||||
|
card["foreignData"] =
|
||||||
|
QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")};
|
||||||
|
QJsonArray cards{card};
|
||||||
|
|
||||||
|
// cardLang defaults to "en" — foreignData must never be imported
|
||||||
|
importer->importCardsFromSet(set, cards);
|
||||||
|
importer->applyLocalizedData();
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Lightning Bolt");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
ASSERT_TRUE(result->getLocalizedNames().isEmpty());
|
||||||
|
ASSERT_TRUE(result->getLocalizedTexts().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, UnsupportedLanguageSkipsForeignData)
|
||||||
|
{
|
||||||
|
QJsonObject card = makeCard("Lightning Bolt");
|
||||||
|
card["foreignData"] = QJsonArray{makeForeignEntry("xx", "Kochanie", "Grzmot uderza.")};
|
||||||
|
QJsonArray cards{card};
|
||||||
|
|
||||||
|
importer->setCardLang("xx");
|
||||||
|
importer->importCardsFromSet(set, cards);
|
||||||
|
importer->applyLocalizedData();
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Lightning Bolt");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
ASSERT_TRUE(result->getLocalizedNames().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, NonMatchingLanguageNotCollected)
|
||||||
|
{
|
||||||
|
QJsonObject card = makeCard("Lightning Bolt");
|
||||||
|
card["foreignData"] = QJsonArray{makeForeignEntry("French", "Éclair", "L'Éclair inflige 3 blessures.")};
|
||||||
|
QJsonArray cards{card};
|
||||||
|
|
||||||
|
importer->setCardLang("de");
|
||||||
|
importer->importCardsFromSet(set, cards);
|
||||||
|
importer->applyLocalizedData();
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Lightning Bolt");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
ASSERT_TRUE(result->getLocalizedNames().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, HigherPrioritySetWinsForReprint)
|
||||||
|
{
|
||||||
|
// First printing in a reprint set, then another in a (more authoritative)
|
||||||
|
// core set: the core set's German text must win even though it was seen later.
|
||||||
|
QJsonObject reprintCard = makeCard("Lightning Bolt");
|
||||||
|
reprintCard["foreignData"] = QJsonArray{makeForeignEntry("German", "Blitzschlag", "Älterer deutscher Text.")};
|
||||||
|
CardSetPtr reprintSet =
|
||||||
|
CardSet::newInstance(controller, "TS2", "Second Set", QString(), QDate(), CardSet::PriorityReprint);
|
||||||
|
importer->setCardLang("de");
|
||||||
|
importer->importCardsFromSet(reprintSet, QJsonArray{reprintCard});
|
||||||
|
|
||||||
|
QJsonObject primaryCard = makeCard("Lightning Bolt");
|
||||||
|
primaryCard["foreignData"] =
|
||||||
|
QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")};
|
||||||
|
CardSetPtr primarySet =
|
||||||
|
CardSet::newInstance(controller, "TS3", "Third Set", QString(), QDate(), CardSet::PriorityPrimary);
|
||||||
|
importer->importCardsFromSet(primarySet, QJsonArray{primaryCard});
|
||||||
|
importer->applyLocalizedData();
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Lightning Bolt");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
ASSERT_EQ(result->getLocalizedName("de"), "Blitzschlag");
|
||||||
|
ASSERT_EQ(result->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu.");
|
||||||
|
ASSERT_EQ(importer->getCardList().size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, HigherPrioritySplitSetWinsForReprint)
|
||||||
|
{
|
||||||
|
// Split cards print each face as its own card object; the joined text is
|
||||||
|
// collected per set with the same priority policy as single-face cards, so a
|
||||||
|
// reprint set's German text must yield to the core set's even when reprints
|
||||||
|
// are imported first.
|
||||||
|
QJsonObject reprintFront = makeCard("Wear // Tear");
|
||||||
|
reprintFront["layout"] = "split";
|
||||||
|
reprintFront["faceName"] = "Wear";
|
||||||
|
reprintFront["side"] = "a";
|
||||||
|
reprintFront["foreignData"] =
|
||||||
|
QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Wear alter Text.")};
|
||||||
|
QJsonObject reprintBack = makeCard("Wear // Tear");
|
||||||
|
reprintBack["layout"] = "split";
|
||||||
|
reprintBack["faceName"] = "Tear";
|
||||||
|
reprintBack["side"] = "b";
|
||||||
|
reprintBack["foreignData"] =
|
||||||
|
QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Tear alter Text.")};
|
||||||
|
CardSetPtr reprintSet =
|
||||||
|
CardSet::newInstance(controller, "TS2", "Second Set", QString(), QDate(), CardSet::PriorityReprint);
|
||||||
|
importer->setCardLang("de");
|
||||||
|
importer->importCardsFromSet(reprintSet, QJsonArray{reprintFront, reprintBack});
|
||||||
|
|
||||||
|
QJsonObject primaryFront = makeCard("Wear // Tear");
|
||||||
|
primaryFront["layout"] = "split";
|
||||||
|
primaryFront["faceName"] = "Wear";
|
||||||
|
primaryFront["side"] = "a";
|
||||||
|
primaryFront["foreignData"] =
|
||||||
|
QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Wear neuer Text.")};
|
||||||
|
QJsonObject primaryBack = makeCard("Wear // Tear");
|
||||||
|
primaryBack["layout"] = "split";
|
||||||
|
primaryBack["faceName"] = "Tear";
|
||||||
|
primaryBack["side"] = "b";
|
||||||
|
primaryBack["foreignData"] =
|
||||||
|
QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Tear neuer Text.")};
|
||||||
|
CardSetPtr primarySet =
|
||||||
|
CardSet::newInstance(controller, "TS3", "Third Set", QString(), QDate(), CardSet::PriorityPrimary);
|
||||||
|
importer->importCardsFromSet(primarySet, QJsonArray{primaryFront, primaryBack});
|
||||||
|
importer->applyLocalizedData();
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Wear // Tear");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
ASSERT_EQ(result->getLocalizedName("de"), "Verschleiß // Zerrreißung");
|
||||||
|
ASSERT_EQ(result->getLocalizedText("de"), "Wear neuer Text.\n\n---\n\nTear neuer Text.");
|
||||||
|
ASSERT_EQ(importer->getCardList().size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(OracleImporterTest, StartImportAppliesLocalizedData)
|
||||||
|
{
|
||||||
|
QJsonObject card = makeCard("Lightning Bolt");
|
||||||
|
card["foreignData"] =
|
||||||
|
QJsonArray{makeForeignEntry("Portuguese (Brazil)", "Raio", "Raio causa 3 de dano a qualquer alvo.")};
|
||||||
|
QJsonObject dataSet;
|
||||||
|
dataSet["code"] = "tst";
|
||||||
|
dataSet["name"] = "Test Set";
|
||||||
|
dataSet["type"] = "expansion";
|
||||||
|
dataSet["releaseDate"] = "2024-01-01";
|
||||||
|
dataSet["cards"] = QJsonArray{card};
|
||||||
|
|
||||||
|
QJsonObject root;
|
||||||
|
root["data"] = QJsonObject{{"TST", dataSet}};
|
||||||
|
|
||||||
|
importer->setCardLang("pt");
|
||||||
|
ASSERT_TRUE(importer->readSetsFromByteArray(QJsonDocument(root).toJson(QJsonDocument::Compact)));
|
||||||
|
ASSERT_EQ(importer->startImport(), 1);
|
||||||
|
|
||||||
|
auto result = importer->getCardList().value("Lightning Bolt");
|
||||||
|
ASSERT_FALSE(result.isNull());
|
||||||
|
ASSERT_EQ(result->getLocalizedName("pt"), "Raio");
|
||||||
|
ASSERT_EQ(result->getLocalizedText("pt"), "Raio causa 3 de dano a qualquer alvo.");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
|
|
||||||
|
|
@ -549,6 +549,19 @@ TEST_F(SettingsDefaultsTest, CardsDisplay_ArrowDrawAnimation_Default)
|
||||||
ASSERT_EQ(s.getArrowDrawAnimation(), true);
|
ASSERT_EQ(s.getArrowDrawAnimation(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SettingsDefaultsTest, CardsDisplay_CardLang_Default)
|
||||||
|
{
|
||||||
|
CardsDisplaySettings s(settingsPath, nullptr);
|
||||||
|
ASSERT_EQ(s.getCardLang(), QString("en"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SettingsDefaultsTest, CardsDisplay_CardLang_SetAndGet)
|
||||||
|
{
|
||||||
|
CardsDisplaySettings s(settingsPath, nullptr);
|
||||||
|
s.setCardLang("de");
|
||||||
|
ASSERT_EQ(s.getCardLang(), QString("de"));
|
||||||
|
}
|
||||||
|
|
||||||
// --- VisualDeckStorageSettings ---
|
// --- VisualDeckStorageSettings ---
|
||||||
|
|
||||||
TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)
|
TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue