feat: disable language/translations selection

- make it conditionally available for testing via
Settings/Advanced/Enable translations
- display a tooltip and infobox explaining the current and future
situation
- upon app start, force the language to "en" if the translations are not
enabled; and suggest app restart when they get disabled

Fixes #14527
This commit is contained in:
Lukáš Tinkl 2024-04-29 14:02:19 +02:00 committed by Lukáš Tinkl
parent 85066fccc8
commit d0658feb26
9 changed files with 157 additions and 68 deletions

View File

@ -4,7 +4,7 @@ import ../../constants
# Local App Settings keys: # Local App Settings keys:
const LAS_KEY_LANGUAGE* = "global/language" const LAS_KEY_LANGUAGE* = "global/language"
const DEFAULT_LOCALE = "en" const DEFAULT_LAS_KEY_LANGUAGE* = "en"
const LAS_KEY_THEME* = "global/theme" const LAS_KEY_THEME* = "global/theme"
const DEFAULT_THEME = 2 #system theme, from qml const DEFAULT_THEME = 2 #system theme, from qml
const LAS_KEY_GEOMETRY = "global/app_geometry" const LAS_KEY_GEOMETRY = "global/app_geometry"
@ -20,6 +20,8 @@ const LAS_KEY_FAKE_LOADING_SCREEN_ENABLED = "global/fake_loading_screen"
let DEFAULT_FAKE_LOADING_SCREEN_ENABLED = defined(production) and not TEST_MODE_ENABLED #enabled in production, disabled in development and e2e tests let DEFAULT_FAKE_LOADING_SCREEN_ENABLED = defined(production) and not TEST_MODE_ENABLED #enabled in production, disabled in development and e2e tests
const LAS_KEY_SHARDED_COMMUNITIES_ENABLED = "global/sharded_communities" const LAS_KEY_SHARDED_COMMUNITIES_ENABLED = "global/sharded_communities"
const DEFAULT_LAS_KEY_SHARDED_COMMUNITIES_ENABLED = false const DEFAULT_LAS_KEY_SHARDED_COMMUNITIES_ENABLED = false
const LAS_KEY_TRANSLATIONS_ENABLED = "global/translations_enabled"
const DEFAULT_LAS_KEY_TRANSLATIONS_ENABLED = false
QtObject: QtObject:
type LocalAppSettings* = ref object of QObject type LocalAppSettings* = ref object of QObject
@ -42,7 +44,7 @@ QtObject:
proc languageChanged*(self: LocalAppSettings) {.signal.} proc languageChanged*(self: LocalAppSettings) {.signal.}
proc getLanguage*(self: LocalAppSettings): string {.slot.} = proc getLanguage*(self: LocalAppSettings): string {.slot.} =
self.settings.value(LAS_KEY_LANGUAGE, newQVariant(DEFAULT_LOCALE)).stringVal self.settings.value(LAS_KEY_LANGUAGE, newQVariant(DEFAULT_LAS_KEY_LANGUAGE)).stringVal
proc setLanguage*(self: LocalAppSettings, value: string) {.slot.} = proc setLanguage*(self: LocalAppSettings, value: string) {.slot.} =
self.settings.setValue(LAS_KEY_LANGUAGE, newQVariant(value)) self.settings.setValue(LAS_KEY_LANGUAGE, newQVariant(value))
self.languageChanged() self.languageChanged()
@ -127,22 +129,6 @@ QtObject:
write = setScrollDeceleration write = setScrollDeceleration
notify = scrollDecelerationChanged notify = scrollDecelerationChanged
proc removeKey*(self: LocalAppSettings, key: string) =
if(self.settings.isNil):
return
self.settings.remove(key)
case key:
of LAS_KEY_LANGUAGE: self.languageChanged()
of LAS_KEY_THEME: self.themeChanged()
of LAS_KEY_GEOMETRY: self.geometryChanged()
of LAS_KEY_VISIBILITY: self.visibilityChanged()
of LAS_KEY_SCROLL_VELOCITY: self.scrollVelocityChanged()
of LAS_KEY_SCROLL_DECELERATION: self.scrollDecelerationChanged()
of LAS_KEY_CUSTOM_MOUSE_SCROLLING_ENABLED: self.isCustomMouseScrollingEnabledChanged()
proc getTestEnvironment*(self: LocalAppSettings): bool {.slot.} = proc getTestEnvironment*(self: LocalAppSettings): bool {.slot.} =
return TEST_MODE_ENABLED return TEST_MODE_ENABLED
@ -174,3 +160,35 @@ QtObject:
read = getWakuV2ShardedCommunitiesEnabled read = getWakuV2ShardedCommunitiesEnabled
write = setWakuV2ShardedCommunitiesEnabled write = setWakuV2ShardedCommunitiesEnabled
notify = wakuV2ShardedCommunitiesEnabledChanged notify = wakuV2ShardedCommunitiesEnabledChanged
proc translationsEnabledChanged*(self: LocalAppSettings) {.signal.}
proc getTranslationsEnabled*(self: LocalAppSettings): bool {.slot.} =
self.settings.value(LAS_KEY_TRANSLATIONS_ENABLED, newQVariant(DEFAULT_LAS_KEY_TRANSLATIONS_ENABLED)).boolVal
proc setTranslationsEnabled*(self: LocalAppSettings, value: bool) {.slot.} =
if value == self.getTranslationsEnabled:
return
self.settings.setValue(LAS_KEY_TRANSLATIONS_ENABLED, newQVariant(value))
self.translationsEnabledChanged()
QtProperty[bool] translationsEnabled:
read = getTranslationsEnabled
write = setTranslationsEnabled
notify = translationsEnabledChanged
proc removeKey*(self: LocalAppSettings, key: string) =
if(self.settings.isNil):
return
self.settings.remove(key)
case key:
of LAS_KEY_LANGUAGE: self.languageChanged()
of LAS_KEY_THEME: self.themeChanged()
of LAS_KEY_GEOMETRY: self.geometryChanged()
of LAS_KEY_VISIBILITY: self.visibilityChanged()
of LAS_KEY_SCROLL_VELOCITY: self.scrollVelocityChanged()
of LAS_KEY_SCROLL_DECELERATION: self.scrollDecelerationChanged()
of LAS_KEY_CUSTOM_MOUSE_SCROLLING_ENABLED: self.isCustomMouseScrollingEnabledChanged()
of LAS_KEY_FAKE_LOADING_SCREEN_ENABLED: self.fakeLoadingScreenEnabledChanged()
of LAS_KEY_SHARDED_COMMUNITIES_ENABLED: self.wakuV2ShardedCommunitiesEnabledChanged()
of LAS_KEY_TRANSLATIONS_ENABLED: self.translationsEnabledChanged()

View File

@ -6,6 +6,7 @@ import app/core/main
import constants as main_constants import constants as main_constants
import app/global/global_singleton import app/global/global_singleton
import app/global/local_app_settings
import app/boot/app_controller import app/boot/app_controller
when defined(macosx) and defined(arm64): when defined(macosx) and defined(arm64):
@ -132,6 +133,11 @@ proc mainProc() =
let app = newQGuiApplication() let app = newQGuiApplication()
# force default language ("en") if not "Settings/Advanced/Enable translations"
if not singletonInstance.localAppSettings.getTranslationsEnabled():
if singletonInstance.localAppSettings.getLanguage() != DEFAULT_LAS_KEY_LANGUAGE:
singletonInstance.localAppSettings.setLanguage(DEFAULT_LAS_KEY_LANGUAGE)
# Required by the WalletConnectSDK view right after creating the QGuiApplication instance # Required by the WalletConnectSDK view right after creating the QGuiApplication instance
initializeWebView() initializeWebView()

View File

@ -8,10 +8,19 @@ import AppLayouts.Profile.stores 1.0
import Storybook 1.0 import Storybook 1.0
import utils 1.0 import utils 1.0
import mainui 1.0
SplitView { SplitView {
id: root
Logs { id: logs } Logs { id: logs }
Popups {
popupParent: root
rootStore: QtObject {}
communityTokensStore: QtObject {}
}
SplitView { SplitView {
orientation: Qt.Vertical orientation: Qt.Vertical
SplitView.fillWidth: true SplitView.fillWidth: true
@ -19,8 +28,9 @@ SplitView {
LanguageView { LanguageView {
SplitView.fillWidth: true SplitView.fillWidth: true
SplitView.fillHeight: true SplitView.fillHeight: true
contentWidth: parent.width contentWidth: parent.width - 150
languageSelectionEnabled: ctrlLanguageSelectionEnabled.checked
languageStore: LanguageStore { languageStore: LanguageStore {
property string currentLanguage: "en" property string currentLanguage: "en"
@ -41,6 +51,14 @@ SplitView {
state: 2 state: 2
selected: true selected: true
} }
ListElement {
locale: "cs_CZ"
name: "Czech"
shortName: "čeština"
flag: "🇨🇿"
state: 1
selected: false
}
} }
function changeLanguage(language) { function changeLanguage(language) {
@ -92,16 +110,12 @@ SplitView {
SplitView.preferredHeight: 200 SplitView.preferredHeight: 200
logsView.logText: logs.logText logsView.logText: logs.logText
Switch {
id: ctrlLanguageSelectionEnabled
text: "Language selection enabled"
} }
} }
Control {
SplitView.minimumWidth: 300
SplitView.preferredWidth: 300
font.pixelSize: 13
// model editor will go here
} }
} }

View File

@ -118,9 +118,11 @@ Item {
*/ */
property int menuAlignment: StatusListPicker.MenuAlignment.Right property int menuAlignment: StatusListPicker.MenuAlignment.Right
readonly property alias button: btn
/*! /*!
\qmlproperty enum StatusListPicker::MenuAlignment \qmlproperty enum StatusListPicker::MenuAlignment
This property holds the allignment of the menu in terms of the button This property holds the alignment of the menu in terms of the button
values can be Left, Right or Center values can be Left, Right or Center
*/ */
enum MenuAlignment { enum MenuAlignment {
@ -216,7 +218,7 @@ Item {
contentColor: Theme.palette.primaryColor1 contentColor: Theme.palette.primaryColor1
text: d.selectedItemsText text: d.selectedItemsText
font.pixelSize: 13 font.pixelSize: 13
type: StatusPickerButton.Type.Down type: StatusPickerButton.PickerType.Down
onClicked: { onClicked: {
picker.visible = !picker.visible picker.visible = !picker.visible

View File

@ -5,71 +5,65 @@ import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Controls 0.1
Button { StatusButton {
id: root id: root
property color bgColor: Theme.palette.baseColor2 property color bgColor: Theme.palette.baseColor2
property color contentColor: Theme.palette.baseColor1 property color contentColor: Theme.palette.baseColor1
property var type: StatusPickerButton.Type.Next property int type: StatusPickerButton.PickerType.Next
/*! /*!
\qmlproperty StatusAssetSettings StatusPickerButton::asset \qmlproperty StatusAssetSettings StatusPickerButton::asset
This property holds the image settings information. This property holds the image settings information.
*/ */
property StatusAssetSettings asset: StatusAssetSettings { asset {
width: 20 width: 20
height: 20 height: 20
imgIsIdenticon: false imgIsIdenticon: false
} }
enum Type { enum PickerType {
Next, Next,
Down Down
} }
implicitWidth: 446
implicitHeight: 44
font.pixelSize: 15
horizontalPadding: 16 horizontalPadding: 16
verticalPadding: 3
spacing: 4 spacing: 4
icon.width: 16 icon.width: 16
icon.height: 16 icon.height: 16
background:Rectangle { background: Rectangle {
radius: 8 radius: 8
color: root.bgColor color: root.bgColor
} }
opacity: !root.interactive || !root.enabled ? 0.5 : 1
contentItem: RowLayout { contentItem: RowLayout {
clip: true
spacing: root.spacing spacing: root.spacing
StatusIcon { StatusIcon {
icon: "tiny/chevron-down" icon: "tiny/chevron-down"
visible: root.type === StatusPickerButton.Type.Down visible: root.type === StatusPickerButton.PickerType.Down
Layout.alignment: Qt.AlignVCenter
color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1 color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1
width: root.icon.width width: root.icon.width
height: root.icon.height height: root.icon.height
} }
StatusRoundedImage { StatusRoundedImage {
visible: root.asset.name.toString() !== "" visible: root.asset.name.toString() !== ""
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: root.asset.width Layout.preferredWidth: root.asset.width
Layout.preferredHeight: root.asset.height Layout.preferredHeight: root.asset.height
image.source: root.asset.name image.source: root.asset.name
} }
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter font: root.font
font.pixelSize: root.font.pixelSize
color: root.contentColor color: root.contentColor
text: root.text text: root.text
clip: true
elide: Text.ElideRight elide: Text.ElideRight
} }
StatusIcon { StatusIcon {
icon: "tiny/chevron-right" icon: "tiny/chevron-right"
visible: root.type === StatusPickerButton.Type.Next visible: root.type === StatusPickerButton.PickerType.Next
Layout.alignment: Qt.AlignVCenter
color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1 color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1
width: root.icon.width width: root.icon.width
height: root.icon.height height: root.icon.height

View File

@ -10,9 +10,9 @@ import StatusQ.Core.Theme 0.1
\inqmlmodule StatusQ.Controls \inqmlmodule StatusQ.Controls
\since StatusQ.Controls 0.1 \since StatusQ.Controls 0.1
\brief Displays a customizable WarningBox component. \brief Displays a customizable WarningBox component.
Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html}{Item}. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html}{Control}.
The \c StatusWarningBox displays an customizable WarningBox for users to show an icon and text. The \c StatusWarningBox displays a customizable WarningBox for users to show an icon and text.
For example: For example:
\qml \qml
@ -30,7 +30,6 @@ import StatusQ.Core.Theme 0.1
Control { Control {
id: root id: root
implicitWidth: 614
padding: 16 padding: 16
/*! /*!
@ -70,6 +69,12 @@ Control {
*/ */
property int textSize: Theme.primaryTextFontSize property int textSize: Theme.primaryTextFontSize
/*!
\qmlproperty Component StatusWarningBox::extraContentComponent
This property lets you add some extra component on the trailing side (like a button)
*/
property alias extraContentComponent: extraContent.sourceComponent
background: Rectangle { background: Rectangle {
radius: 8 radius: 8
opacity: 0.5 opacity: 0.5
@ -93,12 +98,12 @@ Control {
StatusBaseText { StatusBaseText {
id: warningText id: warningText
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: contentHeight
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
font.pixelSize: root.textSize font.pixelSize: root.textSize
color: root.textColor color: root.textColor
} }
Loader {
id: extraContent
}
} }
} }

View File

@ -278,6 +278,7 @@ StatusSectionLayout {
implicitWidth: parent.width implicitWidth: parent.width
implicitHeight: parent.height implicitHeight: parent.height
languageSelectionEnabled: localAppSettings.translationsEnabled
languageStore: root.store.languageStore languageStore: root.store.languageStore
currencyStore: root.currencyStore currencyStore: root.currencyStore
sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.language) sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.language)

View File

@ -339,6 +339,30 @@ SettingsContentBase {
width: parent.width width: parent.width
} }
StatusSettingsLineButton {
anchors.leftMargin: 0
anchors.rightMargin: 0
text: qsTr("Enable translations")
isSwitch: true
switchChecked: localAppSettings.translationsEnabled
onClicked: {
localAppSettings.translationsEnabled = !localAppSettings.translationsEnabled
if (!checked)
Global.openPopup(disableLanguagesPopupComponent)
}
}
Component {
id: disableLanguagesPopupComponent
ConfirmationDialog {
destroyOnClose: true
headerSettings.title: qsTr("Language reset")
confirmationText: qsTr("Display language will be switched back to English. You must restart the application for changes to take effect.")
confirmButtonLabel: qsTr("Restart")
onConfirmButtonClicked: Utils.restartApplication()
}
}
// TODO: replace with StatusQ component // TODO: replace with StatusQ component
StatusSettingsLineButton { StatusSettingsLineButton {
anchors.leftMargin: 0 anchors.leftMargin: 0
@ -479,7 +503,7 @@ SettingsContentBase {
onConfirmButtonClicked: { onConfirmButtonClicked: {
root.advancedStore.toggleIsGoerliEnabled() root.advancedStore.toggleIsGoerliEnabled()
close() close()
Qt.quit() Utils.restartApplication()
} }
onCancelButtonClicked: { onCancelButtonClicked: {
close() close()

View File

@ -1,11 +1,12 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Controls 2.13 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.15
import utils 1.0 import utils 1.0
import shared.panels 1.0 import shared.panels 1.0
import shared.popups 1.0 import shared.popups 1.0
import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils import StatusQ.Core.Utils 0.1 as StatusQUtils
@ -23,6 +24,7 @@ SettingsContentBase {
property LanguageStore languageStore property LanguageStore languageStore
property var currencyStore property var currencyStore
property bool languageSelectionEnabled
objectName: "languageView" objectName: "languageView"
onVisibleChanged: { if(!visible) root.setViewIdleState()} onVisibleChanged: { if(!visible) root.setViewIdleState()}
@ -93,6 +95,16 @@ SettingsContentBase {
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
StatusListPicker { StatusListPicker {
id: languagePicker id: languagePicker
button.interactive: root.languageSelectionEnabled
StatusToolTip {
y: parent.height + Style.current.padding
margins: 0
visible: !root.languageSelectionEnabled && languagePicker.button.hovered
orientation: StatusToolTip.Orientation.Bottom
text: qsTr("Translations coming soon")
}
property string newKey property string newKey
function descriptionForState(state) { function descriptionForState(state) {
@ -105,13 +117,13 @@ SettingsContentBase {
inputList: SortFilterProxyModel { inputList: SortFilterProxyModel {
sourceModel: root.languageStore.languageModel sourceModel: root.languageStore.languageModel
// !Don't use proxy roles cause they harm performance a lot!
// "category" is the only role that can't be mocked by StatusListPicker::proxy // "category" is the only role that can't be mocked by StatusListPicker::proxy
// due to StatusListPicker internal implementation limitation (ListView's section.property) // due to StatusListPicker internal implementation limitation (ListView's section.property)
proxyRoles: [ proxyRoles: [
ExpressionRole { FastExpressionRole {
name: "category" name: "category"
expression: languagePicker.descriptionForState(model.state) expression: languagePicker.descriptionForState(model.state)
expectedRoles: ["state"]
} }
] ]
@ -129,7 +141,7 @@ SettingsContentBase {
proxy { proxy {
key: (model) => model.locale key: (model) => model.locale
name: (model) => model.name name: (model) => model.name
shortName: (model) => model.native shortName: (model) => model.native || model.shortName
symbol: (model) => "" symbol: (model) => ""
imageSource: (model) => StatusQUtils.Emoji.iconSource(model.flag) imageSource: (model) => StatusQUtils.Emoji.iconSource(model.flag)
selected: (model) => model.locale === root.languageStore.currentLanguage selected: (model) => model.locale === root.languageStore.currentLanguage
@ -143,13 +155,28 @@ SettingsContentBase {
onItemPickerChanged: { onItemPickerChanged: {
if(selected && root.languageStore.currentLanguage !== key) { if(selected && root.languageStore.currentLanguage !== key) {
root.changeLanguage(key) root.changeLanguage(key)
languageConfirmationDialog.active = true Global.openPopup(languageConfirmationDialog)
languageConfirmationDialog.item.open()
} }
} }
} }
} }
StatusWarningBox {
Layout.fillWidth: true
Layout.bottomMargin: Style.current.padding
borderColor: Theme.palette.baseColor2
textColor: Theme.palette.directColor1
icon: "group-chat"
iconColor: Theme.palette.baseColor1
text: qsTr("We need your help to translate Status, so that together we can bring privacy and free speech to the people everywhere, including those who need it most.")
extraContentComponent: StatusFlatButton {
icon.name: "external-link"
text: qsTr("Learn more")
size: StatusBaseButton.Size.Small
onClicked: Global.openLinkWithConfirmation(Constants.externalStatusLinkWithHttps + '/translations', Constants.externalStatusLinkWithHttps)
}
}
Separator { Separator {
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: Style.current.padding Layout.bottomMargin: Style.current.padding
@ -158,8 +185,7 @@ SettingsContentBase {
// Time format options: // Time format options:
Column { Column {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.current.padding spacing: Constants.settingsSection.itemSpacing
spacing: Style.current.padding
StatusBaseText { StatusBaseText {
text: qsTr("Time Format") text: qsTr("Time Format")
} }
@ -183,15 +209,14 @@ SettingsContentBase {
} }
} }
Loader { Component {
id: languageConfirmationDialog id: languageConfirmationDialog
active: false ConfirmationDialog {
sourceComponent: ConfirmationDialog { destroyOnClose: true
headerSettings.title: qsTr("Change language") headerSettings.title: qsTr("Change language")
confirmationText: qsTr("Display language has been changed. You must restart the application for changes to take effect.") confirmationText: qsTr("Display language has been changed. You must restart the application for changes to take effect.")
confirmButtonLabel: qsTr("Restart") confirmButtonLabel: qsTr("Restart")
onConfirmButtonClicked: { onConfirmButtonClicked: {
languageConfirmationDialog.active = false
Utils.restartApplication() Utils.restartApplication()
} }
} }