0.4.0: interfaccia in italiano e inglese, scelta nelle impostazioni
L'inglese è la lingua sorgente delle stringhe; l'italiano sta nel catalogo translations/harbour-aira-it.ts (99 stringhe), che lupdate aggiorna e lrelease compila in build. Di serie la lingua del sistema, con ripiego sull'inglese. - nuova classe Translator: risolve la lingua prima di createView e allinea QLocale::setDefault, così anche i numeri seguono la lingua (23,0 °C in italiano, 23.0 °C in inglese) — le temperature passano da formatValue, non più dal toFixed() di JavaScript che metteva sempre il punto - Qt 5.6 non ha QQmlEngine::retranslate(): al cambio di lingua la radice QML viene ricaricata (setSource ricrea il componente anche a url invariata) - le etichette di stato in airajson.cpp usano QCoreApplication::translate per esteso: lupdate non riconosce una funzione wrapper e le avrebbe perse - selettore della lingua in Impostazioni (Sistema / English / Italiano) - test: 41/41, carica il .qm vero come fa main.cpp e verifica entrambe le lingue, il ripiego e i formati numerici - anteprime bilingui (docs/anteprima/ inglese, it/ italiano), README e PIANO aggiornati, spec RPM con i cataloghi in %files
This commit is contained in:
+22
-17
@@ -1,8 +1,10 @@
|
||||
#include "airajson.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QLocale>
|
||||
#include <QUrl>
|
||||
|
||||
namespace AiraJson {
|
||||
@@ -49,35 +51,35 @@ QString modeLabel(const QString &operatingStatus)
|
||||
{
|
||||
const QString s = operatingStatus;
|
||||
if (s == QLatin1String("OPERATING_STATUS_AUTO"))
|
||||
return QStringLiteral("Automatico");
|
||||
return QCoreApplication::translate("AiraJson", "Automatic");
|
||||
if (s == QLatin1String("OPERATING_STATUS_HEATING"))
|
||||
return QStringLiteral("Riscaldamento");
|
||||
return QCoreApplication::translate("AiraJson", "Heating");
|
||||
if (s == QLatin1String("OPERATING_STATUS_COOLING"))
|
||||
return QStringLiteral("Raffrescamento");
|
||||
return QCoreApplication::translate("AiraJson", "Cooling");
|
||||
if (s == QLatin1String("OPERATING_STATUS_HOT_WATER"))
|
||||
return QStringLiteral("Acqua calda");
|
||||
return QCoreApplication::translate("AiraJson", "Hot water");
|
||||
if (s == QLatin1String("OPERATING_STATUS_STANDBY"))
|
||||
return QStringLiteral("Stand-by");
|
||||
return QCoreApplication::translate("AiraJson", "Standby");
|
||||
if (s == QLatin1String("OPERATING_STATUS_OFF"))
|
||||
return QStringLiteral("Spento");
|
||||
return QCoreApplication::translate("AiraJson", "Off");
|
||||
// Stato della pompa per zona (state.zoneN_mode).
|
||||
if (s == QLatin1String("PUMP_MODE_STATE_HEATING"))
|
||||
return QStringLiteral("Riscaldamento");
|
||||
return QCoreApplication::translate("AiraJson", "Heating");
|
||||
if (s == QLatin1String("PUMP_MODE_STATE_COOLING"))
|
||||
return QStringLiteral("Raffrescamento");
|
||||
return QCoreApplication::translate("AiraJson", "Cooling");
|
||||
if (s == QLatin1String("PUMP_MODE_STATE_HOT_WATER"))
|
||||
return QStringLiteral("Acqua calda");
|
||||
return QCoreApplication::translate("AiraJson", "Hot water");
|
||||
if (s == QLatin1String("PUMP_MODE_STATE_IDLE"))
|
||||
return QStringLiteral("In attesa");
|
||||
return QCoreApplication::translate("AiraJson", "Idle");
|
||||
if (s == QLatin1String("PUMP_MODE_STATE_STANDBY"))
|
||||
return QStringLiteral("Stand-by");
|
||||
return QCoreApplication::translate("AiraJson", "Standby");
|
||||
if (s == QLatin1String("PUMP_MODE_STATE_OFF"))
|
||||
return QStringLiteral("Spento");
|
||||
return QCoreApplication::translate("AiraJson", "Off");
|
||||
// Stato operativo della pompa (state.pump_active_state).
|
||||
if (s == QLatin1String("PUMP_ACTIVE_STATE_IDLE"))
|
||||
return QStringLiteral("Ferma");
|
||||
return QCoreApplication::translate("AiraJson", "Stopped");
|
||||
if (s == QLatin1String("PUMP_ACTIVE_STATE_RUNNING"))
|
||||
return QStringLiteral("In funzione");
|
||||
return QCoreApplication::translate("AiraJson", "Running");
|
||||
if (s.isEmpty())
|
||||
return QStringLiteral("—");
|
||||
// Sconosciuto: togliamo il prefisso e mostriamo il resto, minuscolo.
|
||||
@@ -97,7 +99,10 @@ QString formatValue(const QVariant &value)
|
||||
bool isNumber = false;
|
||||
const double d = value.toDouble(&isNumber);
|
||||
if (isNumber)
|
||||
return QString::number(d, 'f', 1);
|
||||
// QLocale() = locale predefinito, che translator.cpp allinea alla lingua
|
||||
// dell'app: separatore decimale col punto in inglese, con la virgola in
|
||||
// italiano (QString::number darebbe sempre il punto).
|
||||
return QLocale().toString(d, 'f', 1);
|
||||
|
||||
const QString s = value.toString();
|
||||
return s.isEmpty() ? QStringLiteral("—") : s;
|
||||
@@ -122,8 +127,8 @@ QString errorMessage(const QByteArray &body, int httpStatus)
|
||||
return msg;
|
||||
}
|
||||
if (httpStatus == 0)
|
||||
return QStringLiteral("server non raggiungibile");
|
||||
return QStringLiteral("errore HTTP %1").arg(httpStatus);
|
||||
return QCoreApplication::translate("AiraJson", "server unreachable");
|
||||
return QCoreApplication::translate("AiraJson", "HTTP error %1").arg(httpStatus);
|
||||
}
|
||||
|
||||
} // namespace AiraJson
|
||||
|
||||
+12
-12
@@ -57,7 +57,7 @@ QNetworkReply *ApiClient::send(const QByteArray &verb, const QString &path,
|
||||
{
|
||||
const QString base = AiraJson::normalizeBaseUrl(m_settings ? m_settings->baseUrl() : QString());
|
||||
if (base.isEmpty()) {
|
||||
emit errorOccurred(QStringLiteral("Indirizzo del bridge non configurato"));
|
||||
emit errorOccurred(tr("Bridge address not configured"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ QNetworkReply *ApiClient::send(const QByteArray &verb, const QString &path,
|
||||
void ApiClient::refresh()
|
||||
{
|
||||
if (!configured()) {
|
||||
emit errorOccurred(QStringLiteral("Configura indirizzo e token del bridge"));
|
||||
emit errorOccurred(tr("Set the bridge address and token"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ void ApiClient::refresh()
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError || status != 200) {
|
||||
const QString msg = (reply->error() == QNetworkReply::OperationCanceledError)
|
||||
? QStringLiteral("Server non raggiungibile (timeout)")
|
||||
? tr("Server unreachable (timeout)")
|
||||
: AiraJson::errorMessage(body, status);
|
||||
qWarning() << "[api] summary non riuscito:" << status << msg;
|
||||
emit errorOccurred(msg);
|
||||
@@ -110,7 +110,7 @@ void ApiClient::refresh()
|
||||
const QVariantMap summary = AiraJson::parseSummary(body, &ok);
|
||||
if (!ok) {
|
||||
qWarning() << "[api] riassunto non interpretabile";
|
||||
emit errorOccurred(QStringLiteral("Risposta del bridge non interpretabile"));
|
||||
emit errorOccurred(tr("Unreadable response from the bridge"));
|
||||
return;
|
||||
}
|
||||
m_summary = summary;
|
||||
@@ -122,7 +122,7 @@ void ApiClient::testConnection()
|
||||
{
|
||||
const QString base = AiraJson::normalizeBaseUrl(m_settings ? m_settings->baseUrl() : QString());
|
||||
if (base.isEmpty()) {
|
||||
emit connectionTested(false, QStringLiteral("Inserisci l'indirizzo del bridge"));
|
||||
emit connectionTested(false, tr("Enter the bridge address"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,12 +138,12 @@ void ApiClient::testConnection()
|
||||
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (reply->error() != QNetworkReply::NoError || status != 200) {
|
||||
const QString msg = (reply->error() == QNetworkReply::OperationCanceledError)
|
||||
? QStringLiteral("Timeout: nessuna risposta")
|
||||
: QStringLiteral("Irraggiungibile (HTTP %1)").arg(status);
|
||||
? tr("Timeout: no response")
|
||||
: tr("Unreachable (HTTP %1)").arg(status);
|
||||
emit connectionTested(false, msg);
|
||||
return;
|
||||
}
|
||||
emit connectionTested(true, QStringLiteral("Bridge raggiungibile"));
|
||||
emit connectionTested(true, tr("Bridge reachable"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ QString ApiClient::formatValue(const QVariant &value) const
|
||||
void ApiClient::sendCommand(const QString &id, const QVariant &value)
|
||||
{
|
||||
if (!configured()) {
|
||||
emit commandFinished(false, QStringLiteral("Configura indirizzo e token del bridge"));
|
||||
emit commandFinished(false, tr("Set the bridge address and token"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ void ApiClient::sendCommand(const QString &id, const QVariant &value)
|
||||
|
||||
QNetworkReply *reply = send("POST", QStringLiteral("/command"), payload, 30);
|
||||
if (!reply) {
|
||||
emit commandFinished(false, QStringLiteral("Bridge non configurato"));
|
||||
emit commandFinished(false, tr("Bridge not configured"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,14 +183,14 @@ void ApiClient::sendCommand(const QString &id, const QVariant &value)
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError || status != 200) {
|
||||
const QString msg = (reply->error() == QNetworkReply::OperationCanceledError)
|
||||
? QStringLiteral("Timeout: comando non inviato")
|
||||
? tr("Timeout: command not sent")
|
||||
: AiraJson::errorMessage(body, status);
|
||||
qWarning() << "[api] comando" << id << "fallito:" << status << msg;
|
||||
emit commandFinished(false, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
emit commandFinished(true, QStringLiteral("Comando inviato"));
|
||||
emit commandFinished(true, tr("Command sent"));
|
||||
refresh(); // i valori letti cambiano: rileggo
|
||||
});
|
||||
}
|
||||
|
||||
+24
-2
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "apiclient.h"
|
||||
#include "settings.h"
|
||||
#include "translator.h"
|
||||
|
||||
Q_DECL_EXPORT int main(int argc, char *argv[])
|
||||
{
|
||||
@@ -20,19 +21,40 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
|
||||
|
||||
app->setOrganizationName(QStringLiteral("harbour"));
|
||||
app->setApplicationName(QStringLiteral("aira"));
|
||||
app->setApplicationVersion(QStringLiteral("0.3.0"));
|
||||
app->setApplicationVersion(QStringLiteral("0.4.0"));
|
||||
|
||||
Settings settings;
|
||||
ApiClient api(&settings);
|
||||
|
||||
// Lingua dell'interfaccia. Va risolta PRIMA di createView: le qsTr() del QML
|
||||
// si valutano alla creazione dei componenti, quindi un traduttore installato
|
||||
// dopo non avrebbe effetto sulla prima schermata.
|
||||
Translator translator(&settings);
|
||||
translator.applySaved();
|
||||
|
||||
QQuickView *view = SailfishApp::createView();
|
||||
|
||||
// Prima riga del log: dice subito se il device sta girando la build nuova
|
||||
// o una stale (trappola nota del build engine).
|
||||
qDebug() << "harbour-aira v0.3.0 build" << __DATE__ << __TIME__;
|
||||
qDebug() << "harbour-aira v0.4.0 build" << __DATE__ << __TIME__;
|
||||
|
||||
view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings);
|
||||
view->rootContext()->setContextProperty(QStringLiteral("api"), &api);
|
||||
view->rootContext()->setContextProperty(QStringLiteral("translator"), &translator);
|
||||
|
||||
// Qt 5.6 non ha QQmlEngine::retranslate() (arriva con Qt 5.10): installare un
|
||||
// traduttore nuovo NON ri-traduce le stringhe gia' mostrate, quindi la lingua
|
||||
// scelta nelle impostazioni si applica ricaricando la radice QML. Conseguenza
|
||||
// voluta e attesa: si torna alla home, nella lingua nuova.
|
||||
//
|
||||
// setSource() con la STESSA url ricrea davvero il componente: in Qt 5.6
|
||||
// QQuickViewPrivate::execute() cancella root e component e ne costruisce uno
|
||||
// nuovo (documentato: "the QML component being instantiated, even if the URL
|
||||
// is unchanged"), quindi ogni qsTr() viene rivalutato. Non serve
|
||||
// clearComponentCache(): i valori delle stringhe non stanno nella cache.
|
||||
QObject::connect(&translator, &Translator::languageChanged, view, [view]() {
|
||||
view->setSource(SailfishApp::pathTo(QStringLiteral("qml/harbour-aira.qml")));
|
||||
});
|
||||
|
||||
view->setSource(SailfishApp::pathTo(QStringLiteral("qml/harbour-aira.qml")));
|
||||
view->show();
|
||||
|
||||
@@ -52,3 +52,18 @@ void Settings::setToken(const QString &value)
|
||||
m_settings.sync();
|
||||
emit tokenChanged();
|
||||
}
|
||||
|
||||
QString Settings::language() const
|
||||
{
|
||||
// Vuoto = non deciso dall'utente: si usa la lingua del sistema.
|
||||
return m_settings.value(QStringLiteral("language"), QString()).toString();
|
||||
}
|
||||
|
||||
void Settings::setLanguage(const QString &value)
|
||||
{
|
||||
if (value == language())
|
||||
return;
|
||||
m_settings.setValue(QStringLiteral("language"), value);
|
||||
m_settings.sync();
|
||||
emit languageChanged();
|
||||
}
|
||||
|
||||
+8
-1
@@ -10,13 +10,16 @@
|
||||
// sparivano quando l'app veniva avviata dal launcher.
|
||||
//
|
||||
// baseUrl indirizzo del bridge, es. "192.168.2.50:8790" (schema opzionale)
|
||||
// token token del bridge (Authorization: Bearer ...)
|
||||
// token token del bridge (Authorization: Bearer ***)
|
||||
// language lingua dell'interfaccia: "" = lingua del sistema (ripiego
|
||||
// sull'inglese), "it" = italiano.
|
||||
class Settings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString baseUrl READ baseUrl WRITE setBaseUrl NOTIFY baseUrlChanged)
|
||||
Q_PROPERTY(QString token READ token WRITE setToken NOTIFY tokenChanged)
|
||||
Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged)
|
||||
|
||||
public:
|
||||
explicit Settings(QObject *parent = 0);
|
||||
@@ -27,9 +30,13 @@ public:
|
||||
QString token() const;
|
||||
void setToken(const QString &value);
|
||||
|
||||
QString language() const;
|
||||
void setLanguage(const QString &value);
|
||||
|
||||
signals:
|
||||
void baseUrlChanged();
|
||||
void tokenChanged();
|
||||
void languageChanged();
|
||||
|
||||
private:
|
||||
static QString configPath();
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "translator.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QLocale>
|
||||
|
||||
namespace {
|
||||
// qmake installa i cataloghi sotto il TARGET, non sotto org/app: e' li' che
|
||||
// l'app deve cercarli (vedi CONFIG += sailfishapp_i18n nel .pro).
|
||||
const char *kTranslationsDir = "/usr/share/harbour-aira/translations";
|
||||
const char *kCatalogBase = "harbour-aira";
|
||||
}
|
||||
|
||||
Translator::Translator(Settings *settings, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
QString Translator::language() const
|
||||
{
|
||||
return m_settings ? m_settings->language() : QString();
|
||||
}
|
||||
|
||||
void Translator::setLanguage(const QString &value)
|
||||
{
|
||||
const QString before = language();
|
||||
if (m_settings)
|
||||
m_settings->setLanguage(value); // persiste subito
|
||||
if (value == before)
|
||||
return; // nessun cambio: niente ricarica
|
||||
apply(value);
|
||||
emit languageChanged();
|
||||
}
|
||||
|
||||
void Translator::applySaved()
|
||||
{
|
||||
// All'avvio non si ricarica nulla (la view non esiste ancora): nessun segnale.
|
||||
apply(language());
|
||||
}
|
||||
|
||||
void Translator::apply(const QString &value)
|
||||
{
|
||||
if (m_translator) {
|
||||
QCoreApplication::removeTranslator(m_translator);
|
||||
delete m_translator;
|
||||
}
|
||||
|
||||
// Lingua vuota = lingua del telefono. Attenzione: QLocale() sarebbe il locale
|
||||
// PREDEFINITO, che qui sotto cambiamo noi con setDefault(): per tornare alla
|
||||
// lingua di sistema serve QLocale::system(), che non risente di setDefault.
|
||||
const QLocale locale = value.isEmpty() ? QLocale::system() : QLocale(value);
|
||||
QTranslator *translator = new QTranslator(this);
|
||||
|
||||
// Anche il locale predefinito segue la lingua scelta: da questo dipendono i
|
||||
// formati dei numeri (virgola o punto) e le date.
|
||||
QLocale::setDefault(locale);
|
||||
|
||||
if (translator->load(locale, QString::fromLatin1(kCatalogBase),
|
||||
QStringLiteral("-"), QString::fromLatin1(kTranslationsDir))) {
|
||||
QCoreApplication::installTranslator(translator);
|
||||
m_translator = translator;
|
||||
} else {
|
||||
// Nessun catalogo (es. locale inglese, o .qm non installati): si resta
|
||||
// sulle stringhe sorgente inglesi. Il traduttore non va installato.
|
||||
delete translator;
|
||||
}
|
||||
|
||||
qDebug() << "Translator: richiesta ["
|
||||
<< (value.isEmpty() ? QStringLiteral("sistema") : value)
|
||||
<< "] locale" << locale.name()
|
||||
<< "catalogo caricato:" << !m_translator.isNull();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef TRANSLATOR_H
|
||||
#define TRANSLATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QTranslator>
|
||||
|
||||
class Settings;
|
||||
|
||||
// Lingua dell'interfaccia.
|
||||
//
|
||||
// "" (vuoto) -> lingua del sistema (QLocale::system(), non QLocale(), che e' il
|
||||
// locale predefinito e viene cambiato da setDefault), con ripiego
|
||||
// sull'inglese: l'inglese e' la lingua sorgente delle stringhe,
|
||||
// quindi se per il locale del telefono non esiste un catalogo Qt
|
||||
// lascia il testo originale inglese.
|
||||
// "it" -> italiano, dal catalogo installato in
|
||||
// /usr/share/harbour-aira/translations (i .qm li genera qmake in
|
||||
// build con CONFIG += sailfishapp_i18n).
|
||||
//
|
||||
// Qt 5.6 (Sailfish) non ha QQmlEngine::retranslate(): esiste da Qt 5.10, e prima
|
||||
// il QML non si ri-traduce da solo quando cambia il traduttore. Per questo
|
||||
// main.cpp collega languageChanged() alla ricarica della radice QML: l'effetto
|
||||
// visibile e' che l'app torna alla home, ma nella lingua nuova.
|
||||
class Translator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged)
|
||||
|
||||
public:
|
||||
explicit Translator(Settings *settings, QObject *parent = 0);
|
||||
|
||||
QString language() const;
|
||||
void setLanguage(const QString &value); // salva nelle impostazioni e applica
|
||||
void applySaved(); // all'avvio, PRIMA di creare la view
|
||||
|
||||
signals:
|
||||
void languageChanged();
|
||||
|
||||
private:
|
||||
void apply(const QString &value);
|
||||
|
||||
Settings *m_settings;
|
||||
QPointer<QTranslator> m_translator;
|
||||
};
|
||||
|
||||
#endif // TRANSLATOR_H
|
||||
Reference in New Issue
Block a user