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:
+30
-30
@@ -17,7 +17,7 @@ Page {
|
||||
}
|
||||
|
||||
function flag(key) {
|
||||
return page.raw(key) ? qsTr("sì") : qsTr("no")
|
||||
return page.raw(key) ? qsTr("yes") : qsTr("no")
|
||||
}
|
||||
|
||||
function stato(key) {
|
||||
@@ -51,7 +51,7 @@ Page {
|
||||
width: parent.width
|
||||
spacing: Theme.paddingSmall
|
||||
|
||||
PageHeader { title: qsTr("Altri comandi") }
|
||||
PageHeader { title: qsTr("Other commands") }
|
||||
|
||||
Label {
|
||||
visible: page.notice.length > 0
|
||||
@@ -64,80 +64,80 @@ Page {
|
||||
}
|
||||
|
||||
// ---- stato attuale -------------------------------------------- //
|
||||
SectionHeader { text: qsTr("Stato attuale") }
|
||||
SectionHeader { text: qsTr("Current state") }
|
||||
|
||||
InfoRow { label: qsTr("Assenza"); value: page.flag("away_mode") }
|
||||
InfoRow { label: qsTr("Away mode"); value: page.flag("away_mode") }
|
||||
InfoRow { label: qsTr("Night mode"); value: page.flag("night_mode") }
|
||||
InfoRow { label: qsTr("Riscaldamento forzato"); value: page.flag("force_heating") }
|
||||
InfoRow { label: qsTr("Acqua calda attiva"); value: page.flag("hot_water_heating") }
|
||||
InfoRow { label: qsTr("Pompa"); value: page.stato("pump_active_state") }
|
||||
InfoRow { label: qsTr("Modalità consentite"); value: page.stato("configured_modes") }
|
||||
InfoRow { label: qsTr("Forced heating"); value: page.flag("force_heating") }
|
||||
InfoRow { label: qsTr("Hot water active"); value: page.flag("hot_water_heating") }
|
||||
InfoRow { label: qsTr("Pump"); value: page.stato("pump_active_state") }
|
||||
InfoRow { label: qsTr("Allowed modes"); value: page.stato("configured_modes") }
|
||||
|
||||
// ---- assenza --------------------------------------------------- //
|
||||
SectionHeader { text: qsTr("Assenza") }
|
||||
SectionHeader { text: qsTr("Away mode") }
|
||||
|
||||
ActionButton {
|
||||
text: qsTr("Attiva assenza")
|
||||
text: qsTr("Turn away mode on")
|
||||
onTriggered: api.sendCommand("away_mode_on")
|
||||
}
|
||||
ActionButton {
|
||||
text: qsTr("Disattiva assenza")
|
||||
text: qsTr("Turn away mode off")
|
||||
onTriggered: api.sendCommand("away_mode_off")
|
||||
}
|
||||
|
||||
// ---- rumore ---------------------------------------------------- //
|
||||
SectionHeader { text: qsTr("Rumore") }
|
||||
SectionHeader { text: qsTr("Noise") }
|
||||
|
||||
ActionButton {
|
||||
text: qsTr("Night mode per un'ora")
|
||||
text: qsTr("Night mode for one hour")
|
||||
onTriggered: api.sendCommand("night_mode_1h")
|
||||
}
|
||||
|
||||
// ---- riscaldamento --------------------------------------------- //
|
||||
SectionHeader { text: qsTr("Riscaldamento") }
|
||||
SectionHeader { text: qsTr("Heating") }
|
||||
|
||||
ActionButton {
|
||||
text: qsTr("Forza riscaldamento")
|
||||
confirmText: qsTr("Avvio del riscaldamento forzato")
|
||||
text: qsTr("Force heating")
|
||||
confirmText: qsTr("Starting forced heating")
|
||||
onTriggered: api.sendCommand("force_heating_on")
|
||||
}
|
||||
ActionButton {
|
||||
text: qsTr("Ferma riscaldamento forzato")
|
||||
text: qsTr("Stop forced heating")
|
||||
onTriggered: api.sendCommand("force_heating_off")
|
||||
}
|
||||
ActionButton {
|
||||
text: qsTr("Abilita funzione riscaldamento")
|
||||
text: qsTr("Enable heating function")
|
||||
onTriggered: api.sendCommand("heating_on")
|
||||
}
|
||||
ActionButton {
|
||||
text: qsTr("Disabilita funzione riscaldamento")
|
||||
confirmText: qsTr("Il riscaldamento verrà disabilitato")
|
||||
text: qsTr("Disable heating function")
|
||||
confirmText: qsTr("Heating will be disabled")
|
||||
onTriggered: api.sendCommand("heating_off")
|
||||
}
|
||||
|
||||
// ---- acqua calda ----------------------------------------------- //
|
||||
SectionHeader { text: qsTr("Acqua calda sanitaria") }
|
||||
SectionHeader { text: qsTr("Domestic hot water") }
|
||||
|
||||
ActionButton {
|
||||
text: qsTr("Ciclo antilegionella")
|
||||
confirmText: qsTr("Avvio del ciclo antilegionella: richiede tempo")
|
||||
text: qsTr("Legionella cycle")
|
||||
confirmText: qsTr("Starting the legionella cycle: it takes a while")
|
||||
onTriggered: api.sendCommand("legionella_cycle")
|
||||
}
|
||||
ActionButton {
|
||||
text: qsTr("Abilita riscaldamento ACS")
|
||||
text: qsTr("Enable DHW heating")
|
||||
onTriggered: api.sendCommand("hot_water_heating_on")
|
||||
}
|
||||
ActionButton {
|
||||
text: qsTr("Disabilita riscaldamento ACS")
|
||||
confirmText: qsTr("L'acqua calda smetterà di essere riscaldata")
|
||||
text: qsTr("Disable DHW heating")
|
||||
confirmText: qsTr("Domestic hot water will stop being heated")
|
||||
onTriggered: api.sendCommand("hot_water_heating_off")
|
||||
}
|
||||
|
||||
// ---- diagnostica ----------------------------------------------- //
|
||||
SectionHeader { text: qsTr("Diagnostica") }
|
||||
SectionHeader { text: qsTr("Diagnostics") }
|
||||
|
||||
ActionButton {
|
||||
text: qsTr("Verifica impianto")
|
||||
text: qsTr("Plant check")
|
||||
onTriggered: api.sendCommand("ping")
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ Page {
|
||||
Label {
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
text: qsTr("I comandi passano dal bridge e possono richiedere qualche secondo. Dopo l'invio lo stato si aggiorna da solo.")
|
||||
text: qsTr("Commands go through the bridge and may take a few seconds. The status refreshes by itself afterwards.")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeExtraSmall
|
||||
wrapMode: Text.Wrap
|
||||
@@ -163,4 +163,4 @@ Page {
|
||||
if (api.configured)
|
||||
api.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
-35
@@ -38,12 +38,16 @@ Page {
|
||||
return api.summary[key]
|
||||
}
|
||||
|
||||
// I numeri si formattano nel C++ (ApiClient::formatValue): cosi' il
|
||||
// separatore decimale segue la lingua dell'app, non il toFixed di JS.
|
||||
function fmt(value) { return api.formatValue(value) }
|
||||
|
||||
function temp(key) {
|
||||
return api.formatValue(page.raw(key))
|
||||
}
|
||||
|
||||
function flag(key) {
|
||||
return page.raw(key) ? qsTr("sì") : qsTr("no")
|
||||
return page.raw(key) ? qsTr("yes") : qsTr("no")
|
||||
}
|
||||
|
||||
function count(key) {
|
||||
@@ -98,15 +102,15 @@ Page {
|
||||
// fanno deref su null (TypeError + crash).
|
||||
PullDownMenu {
|
||||
MenuItem {
|
||||
text: qsTr("Aggiorna")
|
||||
text: qsTr("Refresh")
|
||||
onClicked: api.refresh()
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("Impostazioni")
|
||||
text: qsTr("Settings")
|
||||
onClicked: page.openSettings()
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("Altri comandi")
|
||||
text: qsTr("Other commands")
|
||||
onClicked: page.openCommands()
|
||||
}
|
||||
}
|
||||
@@ -116,7 +120,7 @@ Page {
|
||||
width: parent.width
|
||||
spacing: Theme.paddingSmall
|
||||
|
||||
PageHeader { title: qsTr("Aira") }
|
||||
PageHeader { title: "Aira" }
|
||||
|
||||
// ---- avvisi ---------------------------------------------------- //
|
||||
Label {
|
||||
@@ -134,7 +138,7 @@ Page {
|
||||
visible: !api.configured
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
text: qsTr("Bridge non configurato. Apri le impostazioni e inserisci l'indirizzo del server e il token.")
|
||||
text: qsTr("Bridge not configured. Open the settings and enter the server address and the token.")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.Wrap
|
||||
@@ -143,7 +147,7 @@ Page {
|
||||
Button {
|
||||
visible: !api.configured
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Impostazioni")
|
||||
text: qsTr("Settings")
|
||||
onClicked: page.openSettings()
|
||||
}
|
||||
|
||||
@@ -151,7 +155,7 @@ Page {
|
||||
Label {
|
||||
visible: api.configured
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Temperatura in casa")
|
||||
text: qsTr("House temperature")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
@@ -179,7 +183,7 @@ Page {
|
||||
Label {
|
||||
visible: api.configured
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("obiettivo %1 °C · %2").arg(page.temp("zone1_setpoint")).arg(api.modeLabel(page.raw("zone1_mode")))
|
||||
text: qsTr("target %1 °C · %2").arg(page.temp("zone1_setpoint")).arg(api.modeLabel(page.raw("zone1_mode")))
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeExtraSmall
|
||||
}
|
||||
@@ -188,12 +192,12 @@ Page {
|
||||
id: casaSlider
|
||||
visible: api.configured
|
||||
width: parent.width
|
||||
label: qsTr("Nuovo obiettivo casa")
|
||||
label: qsTr("New house target")
|
||||
minimumValue: 5
|
||||
maximumValue: 30
|
||||
stepSize: 0.5
|
||||
value: page.targetCasa
|
||||
valueText: value.toFixed(1) + " °C"
|
||||
valueText: page.fmt(value) + " °C"
|
||||
onValueChanged: {
|
||||
page.targetCasaTouched = true
|
||||
page.targetCasa = Math.round(value * 2) / 2
|
||||
@@ -209,14 +213,14 @@ Page {
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: qsTr("Imposta casa a %1 °C").arg(page.targetCasa.toFixed(1))
|
||||
text: qsTr("Set house to %1 °C").arg(page.fmt(page.targetCasa))
|
||||
onClicked: remorse.execute(setCasaButton,
|
||||
qsTr("Temperatura della casa a %1 °C").arg(page.targetCasa.toFixed(1)),
|
||||
qsTr("House temperature to %1 °C").arg(page.fmt(page.targetCasa)),
|
||||
function() { api.sendCommand("set_heating_setpoint", page.targetCasa) })
|
||||
}
|
||||
|
||||
// ---- acqua calda sanitaria ------------------------------------- //
|
||||
SectionHeader { visible: api.configured; text: qsTr("Acqua calda sanitaria") }
|
||||
SectionHeader { visible: api.configured; text: qsTr("Domestic hot water") }
|
||||
|
||||
Label {
|
||||
visible: api.configured
|
||||
@@ -240,19 +244,19 @@ Page {
|
||||
|
||||
InfoRow {
|
||||
visible: api.configured
|
||||
label: qsTr("Obiettivo acqua calda")
|
||||
label: qsTr("DHW target")
|
||||
value: page.temp("dhw_target") + " °C"
|
||||
}
|
||||
|
||||
Slider {
|
||||
visible: api.configured
|
||||
width: parent.width
|
||||
label: qsTr("Nuovo obiettivo")
|
||||
label: qsTr("New target")
|
||||
minimumValue: 45
|
||||
maximumValue: 60
|
||||
stepSize: 1
|
||||
value: page.targetAcs
|
||||
valueText: value + " °C"
|
||||
valueText: page.fmt(value) + " °C"
|
||||
onValueChanged: {
|
||||
page.targetAcsTouched = true
|
||||
page.targetAcs = Math.round(value)
|
||||
@@ -268,9 +272,9 @@ Page {
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: qsTr("Imposta obiettivo %1 °C").arg(page.targetAcs)
|
||||
text: qsTr("Set target %1 °C").arg(page.targetAcs)
|
||||
onClicked: remorse.execute(setAcsButton,
|
||||
qsTr("Obiettivo ACS a %1 °C").arg(page.targetAcs),
|
||||
qsTr("DHW target to %1 °C").arg(page.targetAcs),
|
||||
function() { api.sendCommand("set_hot_water_target", page.targetAcs) })
|
||||
}
|
||||
|
||||
@@ -283,8 +287,8 @@ Page {
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: qsTr("Avvia boost ACS")
|
||||
onClicked: remorse.execute(boostOnButton, qsTr("Avvio del boost ACS"),
|
||||
text: qsTr("Start DHW boost")
|
||||
onClicked: remorse.execute(boostOnButton, qsTr("Starting DHW boost"),
|
||||
function() { api.sendCommand("hot_water_boost_on") })
|
||||
}
|
||||
|
||||
@@ -297,24 +301,24 @@ Page {
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: qsTr("Ferma boost ACS")
|
||||
onClicked: remorse.execute(boostOffButton, qsTr("Arresto del boost ACS"),
|
||||
text: qsTr("Stop DHW boost")
|
||||
onClicked: remorse.execute(boostOffButton, qsTr("Stopping DHW boost"),
|
||||
function() { api.sendCommand("hot_water_boost_off") })
|
||||
}
|
||||
|
||||
// ---- stato impianto ------------------------------------------- //
|
||||
SectionHeader { visible: api.configured; text: qsTr("Impianto") }
|
||||
SectionHeader { visible: api.configured; text: qsTr("Plant") }
|
||||
|
||||
InfoRow { visible: api.configured; label: qsTr("Temperatura esterna"); value: page.temp("outdoor_temperature") + " °C" }
|
||||
InfoRow { visible: api.configured; label: qsTr("Temperatura interna (pompa)"); value: page.temp("indoor_temperature") + " °C" }
|
||||
InfoRow { visible: api.configured; label: qsTr("Modalità"); value: api.modeLabel(page.raw("operating_status")) }
|
||||
InfoRow { visible: api.configured; label: qsTr("Assenza"); value: page.flag("away_mode") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Outdoor temperature"); value: page.temp("outdoor_temperature") + " °C" }
|
||||
InfoRow { visible: api.configured; label: qsTr("Indoor temperature (pump)"); value: page.temp("indoor_temperature") + " °C" }
|
||||
InfoRow { visible: api.configured; label: qsTr("Mode"); value: api.modeLabel(page.raw("operating_status")) }
|
||||
InfoRow { visible: api.configured; label: qsTr("Away mode"); value: page.flag("away_mode") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Night mode"); value: page.flag("night_mode") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Riscaldamento forzato"); value: page.flag("force_heating") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Resistenze integrative"); value: page.flag("inline_heater") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Forced heating"); value: page.flag("force_heating") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Inline heater"); value: page.flag("inline_heater") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Zone"); value: page.count("zones") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Errori"); value: page.count("errors_count") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Aggiornato alle"); value: page.shortTime(page.raw("pump_updated")) }
|
||||
InfoRow { visible: api.configured; label: qsTr("Errors"); value: page.count("errors_count") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Last reading"); value: page.shortTime(page.raw("pump_updated")) }
|
||||
|
||||
Button {
|
||||
visible: api.configured
|
||||
@@ -324,7 +328,7 @@ Page {
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: qsTr("Altri comandi")
|
||||
text: qsTr("Other commands")
|
||||
onClicked: page.openCommands()
|
||||
}
|
||||
|
||||
@@ -339,7 +343,7 @@ Page {
|
||||
visible: api.configured
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
text: qsTr("I comandi vengono inviati al bridge e da lì all'impianto: possono richiedere qualche secondo.")
|
||||
text: qsTr("Commands are sent to the bridge and from there to the plant: they may take a few seconds.")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeExtraSmall
|
||||
wrapMode: Text.Wrap
|
||||
@@ -351,4 +355,4 @@ Page {
|
||||
if (api.configured)
|
||||
api.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ Page {
|
||||
|
||||
function testAndReport() {
|
||||
save()
|
||||
page.testResult = qsTr("verifico…")
|
||||
page.testResult = qsTr("checking…")
|
||||
api.testConnection()
|
||||
}
|
||||
|
||||
@@ -47,13 +47,55 @@ Page {
|
||||
width: parent.width
|
||||
spacing: Theme.paddingMedium
|
||||
|
||||
PageHeader { title: qsTr("Impostazioni") }
|
||||
PageHeader { title: qsTr("Settings") }
|
||||
|
||||
// ---- lingua ---------------------------------------------------- //
|
||||
SectionHeader { text: qsTr("Language") }
|
||||
|
||||
// Le voci si scelgono dal menu: il valore mostrato segue la scelta
|
||||
// salvata (translator.language), non l'ultimo tocco.
|
||||
ComboBox {
|
||||
id: langBox
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
label: qsTr("Language")
|
||||
value: translator.language === "it" ? "Italiano"
|
||||
: translator.language === "en" ? "English"
|
||||
: qsTr("System")
|
||||
menu: ContextMenu {
|
||||
MenuItem {
|
||||
text: qsTr("System")
|
||||
onClicked: translator.language = ""
|
||||
}
|
||||
// I nomi delle lingue restano nella lingua che nominano.
|
||||
MenuItem {
|
||||
text: "English"
|
||||
onClicked: translator.language = "en"
|
||||
}
|
||||
MenuItem {
|
||||
text: "Italiano"
|
||||
onClicked: translator.language = "it"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
text: qsTr("Changing the language reloads the app.")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeExtraSmall
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// ---- bridge ---------------------------------------------------- //
|
||||
SectionHeader { text: qsTr("Bridge") }
|
||||
|
||||
TextField {
|
||||
id: urlField
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
label: qsTr("Indirizzo del bridge")
|
||||
label: qsTr("Bridge address")
|
||||
placeholderText: "192.168.2.50:8790"
|
||||
text: page.urlText
|
||||
onTextChanged: if (text !== page.urlText) page.urlText = text
|
||||
@@ -68,7 +110,7 @@ Page {
|
||||
id: tokenField
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
label: qsTr("Token del bridge")
|
||||
label: qsTr("Bridge token")
|
||||
showEchoModeToggle: true
|
||||
text: page.tokenText
|
||||
onTextChanged: if (text !== page.tokenText) page.tokenText = text
|
||||
@@ -80,7 +122,7 @@ Page {
|
||||
Label {
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
text: qsTr("Indirizzo e token si leggono sul server: il token è in ~/.hermes/profiles/carlo/aira_bridge.json. Senza schema viene usato http://.")
|
||||
text: qsTr("Address and token live on the server: the token is in aira_bridge.json next to the bridge. Without a scheme, http:// is used.")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeExtraSmall
|
||||
wrapMode: Text.Wrap
|
||||
@@ -88,17 +130,17 @@ Page {
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Salva")
|
||||
text: qsTr("Save")
|
||||
onClicked: {
|
||||
page.save()
|
||||
page.testResult = qsTr("impostazioni salvate")
|
||||
page.testResult = qsTr("settings saved")
|
||||
page.testOk = true
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Prova connessione")
|
||||
text: qsTr("Test connection")
|
||||
onClicked: page.testAndReport()
|
||||
}
|
||||
|
||||
@@ -113,4 +155,4 @@ Page {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user