109 lines
5.5 KiB
C++
109 lines
5.5 KiB
C++
|
|
#pragma once
|
|||
|
|
|
|||
|
|
#include <QAtomicInt>
|
|||
|
|
#include <QHash>
|
|||
|
|
#include <QJSValue>
|
|||
|
|
#include <QList>
|
|||
|
|
#include <QObject>
|
|||
|
|
#include <QString>
|
|||
|
|
#include <QStringList>
|
|||
|
|
#include <QVariantList>
|
|||
|
|
#include <QtQml/qqmlregistration.h>
|
|||
|
|
|
|||
|
|
class QQmlEngine;
|
|||
|
|
class QJSEngine;
|
|||
|
|
|
|||
|
|
// Единственный источник всех текстов интерфейса. Каталог — i18n/translations.json,
|
|||
|
|
// он лежит в ресурсах и правится руками; штатных .ts/.qm в проекте нет намеренно.
|
|||
|
|
//
|
|||
|
|
// Из QML читается свойством: text: Loc.t.settings.title
|
|||
|
|
// Из C++ — свободной функцией: Loc::text("settings.title")
|
|||
|
|
//
|
|||
|
|
// Почему свойство, а не Q_INVOKABLE: вызов метода не регистрирует зависимость
|
|||
|
|
// привязки, и text: Loc.t("ключ") никогда бы не обновился при смене языка.
|
|||
|
|
// Чтение же Q_PROPERTY с сигналом NOTIFY зависимость регистрирует, поэтому
|
|||
|
|
// languageChanged перевычисляет все привязки разом. Дальнейшие .settings.title —
|
|||
|
|
// обычные обращения к членам JS-объекта, и отслеживать их не нужно: при смене
|
|||
|
|
// языка дерево заменяется целиком.
|
|||
|
|
//
|
|||
|
|
// Тип свойства — QJSValue, а не QVariantMap: QVariantMap пересобирался бы в новый
|
|||
|
|
// JS-объект при каждом чтении, а привязок в проекте полторы сотни. QJSValue
|
|||
|
|
// строится один раз на смену языка.
|
|||
|
|
class Localization : public QObject
|
|||
|
|
{
|
|||
|
|
Q_OBJECT
|
|||
|
|
QML_NAMED_ELEMENT(Loc)
|
|||
|
|
QML_SINGLETON
|
|||
|
|
|
|||
|
|
Q_PROPERTY(QJSValue t READ t NOTIFY languageChanged)
|
|||
|
|
// Что выбрано пользователем: "system", "ru" или "en".
|
|||
|
|
Q_PROPERTY(QString language READ language NOTIFY languageChanged)
|
|||
|
|
// Во что это разрешилось: всегда "ru" или "en", никогда "system".
|
|||
|
|
Q_PROPERTY(QString resolvedLanguage READ resolvedLanguage NOTIFY languageChanged)
|
|||
|
|
|
|||
|
|
public:
|
|||
|
|
static Localization &instance();
|
|||
|
|
|
|||
|
|
// Фабрика синглтона для движка QML. Объект к этому моменту уже загружен
|
|||
|
|
// из main(), здесь только запоминается движок и строится дерево.
|
|||
|
|
static Localization *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
|
|||
|
|
|
|||
|
|
// Читает каталог из ресурсов и язык из settings.json. Вызывается ровно один
|
|||
|
|
// раз из main() до создания QQmlApplicationEngine. false — каталога нет или
|
|||
|
|
// он битый; это ошибка сборки, и запускаться дальше незачем.
|
|||
|
|
bool load();
|
|||
|
|
|
|||
|
|
QJSValue t() const { return m_tree; }
|
|||
|
|
QString language() const { return m_language; }
|
|||
|
|
QString resolvedLanguage() const;
|
|||
|
|
|
|||
|
|
// Только из GUI-потока: меняет дерево и рассылает languageChanged.
|
|||
|
|
void setLanguage(const QString &code);
|
|||
|
|
|
|||
|
|
// Для комбобокса в настройках: [{ "code": "ru", "name": "Русский" }, …],
|
|||
|
|
// первым пунктом — "system".
|
|||
|
|
Q_INVOKABLE QVariantList availableLanguages() const;
|
|||
|
|
|
|||
|
|
// Рабочие лошадки Loc::text/Loc::list. Потокобезопасны: см. комментарий к
|
|||
|
|
// полям ниже.
|
|||
|
|
[[nodiscard]] QString textFor(const char *key) const;
|
|||
|
|
[[nodiscard]] QStringList listFor(const char *key) const;
|
|||
|
|
|
|||
|
|
signals:
|
|||
|
|
void languageChanged();
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
explicit Localization(QObject *parent = nullptr);
|
|||
|
|
|
|||
|
|
void rebuildTree();
|
|||
|
|
|
|||
|
|
// Каталог заполняется один раз в load() и дальше только читается — поэтому
|
|||
|
|
// обращаться к нему можно из любого потока без блокировок. Единственное, что
|
|||
|
|
// меняется на ходу, — m_index, и он атомарный.
|
|||
|
|
QHash<QString, QStringList> m_singles; // ключ -> значения по языкам
|
|||
|
|
QHash<QString, QList<QStringList>> m_lists; // ключ -> списки по языкам
|
|||
|
|
QStringList m_codes; // ["ru", "en"] — порядок из _meta
|
|||
|
|
QHash<QString, QString> m_displayNames;
|
|||
|
|
|
|||
|
|
QAtomicInt m_index{0}; // индекс текущего языка в m_codes
|
|||
|
|
QString m_language{QStringLiteral("system")};
|
|||
|
|
|
|||
|
|
QJSEngine *m_engine = nullptr;
|
|||
|
|
QJSValue m_tree;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ── Доступ из C++ ───────────────────────────────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// Свободные функции, а не методы: их вызывают и из namespace-обёрток
|
|||
|
|
// (LauncherPaths, JavaLocator, ZlibReference), где никакого QObject нет.
|
|||
|
|
namespace Loc {
|
|||
|
|
|
|||
|
|
// Перевод по ключу. Ключа нет — возвращается сам ключ, в отладочной сборке ещё
|
|||
|
|
// и qWarning: строка вида "launch.error.gameRunning" в интерфейсе сразу видна.
|
|||
|
|
[[nodiscard]] QString text(const char *key);
|
|||
|
|
|
|||
|
|
// Для ключей со значением-списком (модели комбобоксов).
|
|||
|
|
[[nodiscard]] QStringList list(const char *key);
|
|||
|
|
|
|||
|
|
} // namespace Loc
|