diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 3988f9b59..bcefd30fd 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -50,10 +50,29 @@ endif() qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) -qt6_add_translations(${PROJECT} TS_FILES +# -- i18n begin +set(CMAKE_AUTORCC ON) + +set(AMNEZIAVPN_TS_FILES ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts + ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts ) +file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) + +qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES}) + +set(QM_FILE_LIST "") +foreach(FILE ${AMNEZIAVPN_QM_FILES}) + get_filename_component(QM_FILE_NAME ${FILE} NAME) + list(APPEND QM_FILE_LIST "${QM_FILE_NAME}") +endforeach() +string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST}) + +configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) +qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) +# -- i18n end + if(IOS) #execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/run-build-cloak.sh) execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args @@ -324,5 +343,5 @@ if(NOT IOS AND NOT ANDROID) endif() -target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) +target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC}) qt_finalize_target(${PROJECT}) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index f372a1d87..5b6d24918 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -87,6 +87,7 @@ void AmneziaApplication::init() m_vpnConnectionThread.start(); initModels(); + loadTranslator(); initControllers(); #ifdef Q_OS_ANDROID @@ -138,6 +139,7 @@ void AmneziaApplication::init() &ConnectionController::openConnection); connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), &ConnectionController::closeConnection); + connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated); m_engine->load(url); m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); @@ -221,34 +223,27 @@ void AmneziaApplication::loadTranslator() { auto locale = m_settings->getAppLanguage(); m_translator.reset(new QTranslator()); - if (locale != QLocale::English) { - if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { - if (QCoreApplication::installTranslator(m_translator.get())) { - m_settings->setAppLanguage(locale); - } - } - } + updateTranslator(locale); } + void AmneziaApplication::updateTranslator(const QLocale &locale) { - QResource::registerResource(":/translations.qrc"); - if (!m_translator->isEmpty()) + if (!m_translator->isEmpty()) { QCoreApplication::removeTranslator(m_translator.get()); - - if (locale == QLocale::English) { - m_settings->setAppLanguage(locale); - m_engine->retranslate(); } - if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { + QString strFileName = QString(":/translations/amneziavpn")+QLatin1String("_")+locale.name()+".qm"; + if (m_translator->load(strFileName)) { if (QCoreApplication::installTranslator(m_translator.get())) { m_settings->setAppLanguage(locale); } - - m_engine->retranslate(); + } else { + m_settings->setAppLanguage(QLocale::English); } + m_engine->retranslate(); + emit translationsUpdated(); } @@ -338,6 +333,8 @@ void AmneziaApplication::initControllers() m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated); + m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); diff --git a/client/main.cpp b/client/main.cpp index e78a74ff5..396b7625c 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -53,7 +53,6 @@ int main(int argc, char *argv[]) app.setOrganizationName(ORGANIZATION_NAME); app.setApplicationDisplayName(APPLICATION_NAME); - app.loadTranslator(); app.loadFonts(); bool doExec = app.parseCommands(); diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index a4005efbe..d675cd02e 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -101,8 +101,6 @@ void WireguardProtocol::stop() #if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) ErrorCode WireguardProtocol::startMzImpl() { - - qDebug() << "WireguardProtocol::startMzImpl():" << m_rawConfig; m_impl->activate(m_rawConfig); return ErrorCode::NoError; } diff --git a/client/settings.h b/client/settings.h index 919ca7314..f530f6c52 100644 --- a/client/settings.h +++ b/client/settings.h @@ -178,6 +178,15 @@ public: m_settings.setValue("Conf/appLanguage", locale); }; + bool isScreenshotsEnabled() const + { + return m_settings.value("Conf/screenshotsEnabled", false).toBool(); + } + void setScreenshotsEnabled(bool enabled) + { + m_settings.setValue("Conf/screenshotsEnabled", enabled); + } + void clearSettings(); signals: diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index f338216b1..e0bab0185 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2,138 +2,65 @@ - AdvancedServerSettingsLogic + AmneziaApplication - - - Clear server from Amnezia software - - - - - Service: - - - - - Uninstalling Amnezia software... - - - - - Error occurred while cleaning the server. - - - - - - Error message: - - - - - - See logs for details. - - - - - Amnezia server successfully uninstalled - - - - - Error occurred while scanning the server. - - - - - All containers installed on the server are added to the GUI - - - - - No installed containers found on the server + + Split tunneling for WireGuard is not implemented, the option was disabled - AppSettingsLogic + AndroidController - - Software version + + AmneziaVPN - - Save log - - - - - Open backup - - - - - Can't import config, file is corrupted. - - - - - ClientInfoLogic - - - Service: - - - - - ClientManagementLogic - - - Service: - - - - - An error occurred while getting the list of clients. + + VPN Connected + Refers to the app - which is currently running the background and waiting ConnectionController - + VPN Protocols is not installed. Please install VPN container at first - + Connection... - - Disconnect + + Connected - + + Settings updated successfully, Reconnnection... + + + + Reconnection... - - - - + + + + Connect - + Disconnection... @@ -157,24 +84,24 @@ - ContextMenu + ContextMenuType - + C&ut - + &Copy - + &Paste - + &SelectAll @@ -182,70 +109,101 @@ ExportController - + Access error! + + + HomeContainersListView - - Save AmneziaVPN config + + The selected protocol is not supported on the current platform + + + + + Reconnect via VPN Procotol: ImportController - - Open config file + + Scanned %1 of %2. InstallController - - - installed successfully. + + + %1 installed successfully. - - - is already installed on the server. + + + %1 is already installed on the server. - - + + Already installed containers were found on the server. All installed containers have been added to the application - - Server ' + + Settings updated successfully - - ' was removed + + Server '%1' was removed - - All containers from server ' + + All containers from server '%1' have been removed - - has been removed from the server ' + + %1 has been removed from the server '%2' - + Please login as the user + + + Server added successfully + + + + + KeyChainClass + + + Read key failed: %1 + + + + + Write key failed: %1 + + + + + Delete key failed: %1 + + NotificationHandler @@ -266,752 +224,67 @@ Already installed containers were found on the server. All installed containers - + AmneziaVPN notification - + Unsecured network detected: - - PageAbout - - - About Amnezia - - - - - AmneziaVPN is opensource software, it's free forever. Our goal is to make the best VPN client in the world. -<ul> -<li>Sources on <a href="https://github.com/amnezia-vpn/desktop-client">GitHub</a></li> -<li><a href="https://amnezia.org/">Web Site</a></li> -<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a></li> -<li><a href="https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh">Signal group</a></li> -</ul> - - - - - - Support - - - - - Have questions? You can get support by: -<ul> -<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a> (preferred way)</li> -<li>Create issue on <a href="https://github.com/amnezia-vpn/desktop-client/issues">GitHub</a></li> -<li>Email to: <a href="support@amnezia.org">support@amnezia.org</a></li> -</ul> - - - - - Donate - - - - - Please support Amnezia project by donation, we really need it now more than ever. -<ul> -<li>By credit card on <a href="https://www.patreon.com/amneziavpn">Patreon</a> (starting from $1)</li> -<li>Send some coins to addresses listed <a href="https://github.com/amnezia-vpn/desktop-client/blob/master/README.md">on GitHub page</a></li> -</ul> - - - - - - PageAdvancedServerSettings - - - Advanced server settings - - - - - Clients Management - - - - - PageAppSetting - - - Application Settings - - - - - Auto connect - - - - - Auto start - - - - - Start minimized - - - - - Check for updates - - - - - Keep logs - - - - - Open logs folder - - - - - Export logs - - - - - Clear logs - - - - - Cleared - - - - - Backup and restore configuration - - - - - Backup app config - - - - - Restore app config - - - - - PageClientInfoOpenVPN - - - Client Info - - - - - Client name - - - - - Certificate id - - - - - Certificate - - - - - Revoke Certificate - - - - - PageClientInfoWireGuard - - - Client Info - - - - - Client name - - - - - Public Key - - - - - Revoke Key - - - - - PageClientManagement - - - Clients Management - - - PageDeinstalling - - Removing services from + + Removing services from %1 - + Usually it takes no more than 5 minutes - - PageGeneralSettings - - - App settings - - - - - Network settings - - - - - Server Settings - - - - - Share connection - - - - - Servers - - - - - Add server - - - - - Exit - - - PageHome - + VPN protocol - + Servers - - PageNetworkSetting - - - DNS Servers - - - - - Use AmneziaDNS service (recommended) - - - - - Use AmneziaDNS container on your server, when it installed. - -Your AmneziaDNS server available only when it installed and VPN connected, it has internal IP address 172.29.172.254 - -If AmneziaDNS service is not installed on the same server, or this option is unchecked, the following DNS servers will be used: - - - - - Primary DNS server - - - - - - Reset to default - - - - - Secondary DNS server - - - - - PageNewServer - - - Setup your server to use VPN - - - - - If you want easily configure your server just run Wizard - - - - - Run Setup Wizard - - - - - Press configure manually to choose VPN protocols you want to install - - - - - Configure - - - - - PageNewServerProtocols - - - Select VPN protocols - - - - - Setup server - - - - - Select protocol container - - - - - Port - - - - - Network Protocol - - - - - udp - - - - - tcp - - - - - PageProtoCloak - - - Cloak Settings - - - - - Cipher - - - - - chacha20-poly1305 - - - - - aes-256-gcm - - - - - aes-192-gcm - - - - - aes-128-gcm - - - - - Fake Web Site - - - - - Port - - - - - Save and restart VPN - - - - - Cancel - - - - - PageProtoOpenVPN - - - OpenVPN Settings - - - - - VPN Addresses Subnet - - - - - Network protocol - - - - - TCP - - - - - UDP - - - - - Port - - - - - Auto-negotiate encryption - - - - - Cipher - - - - - AES-256-GCM - - - - - AES-192-GCM - - - - - AES-128-GCM - - - - - AES-256-CBC - - - - - AES-192-CBC - - - - - AES-128-CBC - - - - - ChaCha20-Poly1305 - - - - - ARIA-256-CBC - - - - - CAMELLIA-256-CBC - - - - - none - - - - - Hash - - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - Enable TLS auth - - - - - Block DNS requests outside of VPN - - - - - Additional client config commands → - - - - - Additional server config commands → - - - - - Save and restart VPN - - - - - Cancel - - - - - PageProtoSftp - - - SFTP settings - - - - - Port - - - - - User Name - - - - - Password - - - - - Restore drive when client starts - - - - - Mount drive - - - - - PageProtoShadowSocks - - - ShadowSocks Settings - - - - - Cipher - - - - - chacha20-ietf-poly1305 - - - - - xchacha20-ietf-poly1305 - - - - - aes-256-gcm - - - - - aes-192-gcm - - - - - aes-128-gcm - - - - - Port - - - - - Save and restart VPN - - - - - Cancel - - - - - PageProtoTorWebSite - - - Tor Web Site settings - - - - - Web site onion address - - - - - Notes:<ul> -<li>Use <a href="https://www.torproject.org/download/">Tor Browser</a> to open this url.</li> -<li>After installation it takes several minutes while your onion site will become available in the Tor Network.</li> -<li>When configuring WordPress set the domain as this onion address.</li> -</ul> - - - - - - PageProtoWireGuard - - - WireGuard Settings - - - PageProtocolCloakSettings - - Settings updated successfully - - - - + Cloak settings - + Disguised as traffic from - + Port + - Cipher - + Save and Restart Amnezia @@ -1019,195 +292,195 @@ If AmneziaDNS service is not installed on the same server, or this option is unc PageProtocolOpenVpnSettings - - Settings updated successfully - - - - + OpenVPN settings - + VPN Addresses Subnet - + Network protocol - + Port - + Auto-negotiate encryption - - + + Hash - + SHA512 - + SHA384 - + SHA256 - + SHA3-512 - + SHA3-384 - + SHA3-256 - + whirlpool - + BLAKE2b512 - + BLAKE2s256 - + SHA1 - - + + Cipher - + AES-256-GCM - + AES-192-GCM - + AES-128-GCM - + AES-256-CBC - + AES-192-CBC - + AES-128-CBC - + ChaCha20-Poly1305 - + ARIA-256-CBC - + CAMELLIA-256-CBC - + none - + TLS auth - + Block DNS requests outside of VPN - + Additional client configuration commands - - + + Commands: - + Additional server configuration commands - + Remove OpenVPN - + Remove OpenVpn from server? - + + All users with whom you shared a connection will no longer be able to connect to it + + + + Continue Продолжить - + Cancel - + Save and Restart Amnezia @@ -1231,22 +504,26 @@ If AmneziaDNS service is not installed on the same server, or this option is unc - Remove - from server? + Remove %1 from server? + All users with whom you shared a connection will no longer be able to connect to it + + + + Continue Продолжить - + Cancel @@ -1254,177 +531,95 @@ If AmneziaDNS service is not installed on the same server, or this option is unc PageProtocolShadowSocksSettings - - Settings updated successfully - - - - + ShadowSocks settings - + Port - - + + Cipher - + Save and Restart Amnezia - - PageQrDecoderIos - - - Import configuration - - - - - PageServerConfiguringProgress - - - Configuring... - - - - - Please wait. - - - - - Cancel - - - PageServerContainers - - - Install new service + Continue + Продолжить + + + + PageServiceDnsSettings + + + A DNS service is installed on your server, and it is only accessible via VPN. + - - Installed services + + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - - Default + + Remove - - Port + + Remove %1 from server? - - Network Protocol - - - - - udp - - - - - tcp - - - - - Cancel - - - - + Continue Продолжить - - Installed Protocols and Services - - - - - Remove container - - - - - This action will erase all data of this container on the server. - - - - - PageServerList - - - Servers - - - - - PageServerSettings - - - Server settings - - - - - Protocols and Services - - - - - Share Server (FULL ACCESS) - - - - - Advanced server settings - - - - - Forget this server + + Cancel PageServiceSftpSettings - + Settings updated successfully - + SFTP settings - + Host - + + + + + Copied + + + + Port @@ -1434,54 +629,54 @@ If AmneziaDNS service is not installed on the same server, or this option is unc - + Password - + Mount folder on device - + In order to mount remote SFTP folder as local drive, perform following steps: <br> - - + + <br>1. Install the latest version of - - + + <br>2. Install the latest version of - + Detailed instructions - + Remove SFTP and all data stored there - - Some description + + Remove SFTP and all data stored there? - + Continue Продолжить - + Cancel @@ -1489,52 +684,57 @@ If AmneziaDNS service is not installed on the same server, or this option is unc PageServiceTorWebsiteSettings - + Settings updated successfully - + Tor website settings - + Website address - + + Copied + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. - + After installation it takes several minutes while your onion site will become available in the Tor Network. - + When configuring WordPress set the domain as this onion address. - + Remove website - - Some description + + The site with all data will be removed from the tor network. - + Continue Продолжить - + Cancel @@ -1542,32 +742,32 @@ If AmneziaDNS service is not installed on the same server, or this option is unc PageSettings - + Settings - + Servers - + Connection - + Application - + Backup - + About AmneziaVPN @@ -1591,47 +791,67 @@ And if you don't like the app, all the more support it - the donation will - + + https://www.patreon.com/amneziavpn + + + + Show other methods on Github - + Contacts - + Telegram group - + To discuss features - - Mail + + https://t.me/amnezia_vpn_en + Mail + + + + For reviews and bug reports - + Github - + + https://github.com/amnezia-vpn/amnezia-client + + + + Website - + + https://amnezia.org + + + + Check for updates @@ -1644,47 +864,72 @@ And if you don't like the app, all the more support it - the donation will - + + Auto start + + + + + Launch the application every time + + + + + starts + + + + + Start minimized + + + + + Launch application minimized + + + + Language - + Logging - + Enabled - + Disabled - + Reset settings and remove all data from the application - + Reset settings and remove all data from the application? - + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + Continue Продолжить - + Cancel @@ -1692,35 +937,71 @@ And if you don't like the app, all the more support it - the donation will PageSettingsBackup - + Backup - + Settings restored from backup file - + Configuration backup - + It will help you instantly restore connection settings at the next installation - + Make a backup - + + Save backup file + + + + + + Backup files (*.backup) + + + + Restore from backup + + + Open backup file + + + + + Import settings from a backup file? + + + + + All current settings will be reset + + + + + Continue + Продолжить + + + + Cancel + + PageSettingsConnection @@ -1729,43 +1010,53 @@ And if you don't like the app, all the more support it - the donation will Connection + + + Auto connect + + - Use AmneziaDNS if installed on the server + Connect to VPN on app start - - Internal IP address 172.29.172.254 + + Use AmneziaDNS - + + If AmneziaDNS is installed on the server + + + + DNS servers - + If AmneziaDNS is not used or installed - + Split site tunneling - + Allows you to connect to some sites through a secure connection, and to others bypassing it - + Separate application tunneling - + Allows you to use the VPN only for certain applications @@ -1773,45 +1064,115 @@ And if you don't like the app, all the more support it - the donation will PageSettingsDns - + DNS servers - + If AmneziaDNS is not used or installed - + + Primary DNS + + + + + Secondary DNS + + + + + Restore default + + + + + Restore default DNS settings? + + + + + Continue + Продолжить + + + + Cancel + + + + + Settings have been reset + + + + Save + + + Settings saved + + PageSettingsLogging - + Logging - + Save logs - + Open folder with logs - + + Save + + + + + Logs files (*.log) + + + + Save logs to file - + + Clear logs? + + + + + Continue + Продолжить + + + + Cancel + + + + + Logs have been cleaned up + + + + Clear logs @@ -1824,109 +1185,109 @@ And if you don't like the app, all the more support it - the donation will - - No installed containers found - - - - + Clear Amnezia cache - + May be needed when changing other settings - + Clear cached profiles? Очистить закешированные профили - - some description + + No new installed containers found - - - + + + + + + + + Continue Продолжить - - - + + + Cancel - + Check the server for previously installed Amnezia services - + Add them to the application if they were not displayed - + Remove server from application - + Remove server? - + All installed AmneziaVPN services will still remain on the server. - + Clear server from Amnezia software - + Clear server from Amnezia software? - - All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. PageSettingsServerInfo - + Server name - + Save - + Protocols - + Services - + Data @@ -1939,23 +1300,27 @@ And if you don't like the app, all the more support it - the donation will - - + Remove - - from server? + + Remove %1 from server? - + + All users with whom you shared a connection will no longer be able to connect to it + + + + Continue Продолжить - + Cancel @@ -1969,85 +1334,137 @@ And if you don't like the app, all the more support it - the donation will - PageSetupWizard + PageSettingsSplitTunneling - - Setup your server to use VPN + + Only the addresses in the list must be opened via VPN - - High censorship level + + Addresses from the list should never be opened via VPN - - I'm living in a country with a high censorship level. Many of the foreign websites and VPNs are blocked by my government. I want to setup a reliable VPN, which can not be detected by my internet provider and my government. -OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. - + + Split site tunneling - - Medium censorship level + + Mode - - I'm living in a country with a medium censorship level. Some websites are blocked by my government, but VPNs are not blocked at all. I want to setup a flexible solution. -OpenVPN over ShadowSocks profile will be installed. - + + Remove - - Low censorship level + + Continue + Продолжить + + + + Cancel - - I want to improve my privacy on the internet. -OpenVPN profile will be installed. - + + Site or IP - - Next + + Import/Export Sites + + + + + Import + + + + + Save site list + + + + + Save sites + + + + + + + Sites files (*.json) + + + + + Import a list of sites + + + + + Replace site list + + + + + + Open sites file + + + + + Add imported sites to existing ones PageSetupWizardConfigSource - + Server connection - + Do not use connection code from public sources. It may have been created to intercept your data. -It's okay if a friend passed the code. +It's okay as long as it's from someone you trust. - + What do you have? - + File with connection settings - + + File with connection settings or backup + + + + + Open config file + + + + QR-code - + Key as text @@ -2070,43 +1487,37 @@ It's okay if a friend passed the code. - - - Insert - - - - + Password / SSH private key - + Continue Продолжить - + Enter the address in the format 255.255.255.255:88 - + Login to connect via SSH - + Ip address cannot be empty - + Login cannot be empty - + Password/private key cannot be empty @@ -2119,133 +1530,65 @@ It's okay if a friend passed the code. - + Set up a VPN yourself - + I want to choose a VPN protocol - + Continue Продолжить - - - PageSetupWizardHighLevel - - Setup Wizard - - - - - AmneziaVPN will install a VPN protocol which is not visible to your internet provider and government firewall. Your VPN connection will be seen by your internet provider as regular web traffic to a particular website. - -You SHOULD set this website address to some foreign website which is not blocked by your internet provider. In other words, you need to type some foreign website address which is accessible to you without a VPN. - - - - - Type another web site address for masking or keep it by default. Your internet provider will think you working on this web site when you connected to VPN. - - - - - OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. - -This protocol support exporting connection profiles to mobile devices by exporting ShadowSocks and Cloak configs (you should launch the 3rd party open source VPN client - ShadowSocks VPN and install Cloak plugin). - - - - - Next + + Set up later PageSetupWizardInstalling - + The server has already been added to the application - + + Amnesia has detected that your server is currently + + + + + busy installing other software. Amnesia installation + + + + + will pause until the server finishes installing other software + + + + Installing - + + Usually it takes no more than 5 minutes - - PageSetupWizardLowLevel - - - Setup Wizard - - - - - AmneziaVPN will install the OpenVPN protocol with public/private key pairs generated on both server and client sides. - -You can also configure the connection on your mobile device by copying the exported ".ovpn" file to your device, and setting up the official OpenVPN client. - -We recommend not to use messaging applications for sending the connection profile - it contains VPN private keys. - - - - - OpenVPN profile will be installed - - - - - Start configuring - - - - - PageSetupWizardMediumLevel - - - Setup Wizard - - - - - AmneziaVPN will install a VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "VPN masking". - -This protocol supports exporting connection profiles to mobile devices by using QR codes (you should launch the 3rd party open source VPN client - ShadowSocks VPN). - - - - - OpenVPN over ShadowSocks profile will be installed - - - - - Next - - - PageSetupWizardProtocolSettings - Installing - - - - - protocol description + Installing %1 @@ -2254,27 +1597,22 @@ This protocol supports exporting connection profiles to mobile devices by using - - detailed protocol description - - - - + Close - + Network protocol - + Port - + Install @@ -2295,30 +1633,35 @@ This protocol supports exporting connection profiles to mobile devices by using PageSetupWizardQrReader - - Point the camera at the QR code and hold for a couple of seconds. + + Point the camera at the QR code and hold for a couple of seconds. PageSetupWizardStart - + + Settings restored from backup file + + + + Free service for creating a personal VPN on your server. - + Helps you access blocked content without revealing your privacy, even to VPN providers. - + I have the data to connect - + I have nothing @@ -2351,33 +1694,6 @@ This protocol supports exporting connection profiles to mobile devices by using Продолжить - - PageSetupWizardVPNMode - - - Setup Wizard - - - - - Optional. - -You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later. - -Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address. - - - - - Turn on mode "VPN for selected sites" - - - - - Start configuring - - - PageSetupWizardViewConfig @@ -2386,22 +1702,22 @@ Please note, you should add addresses to the list after VPN connection establish - + Do not use connection code from public sources. It could be created to intercept your data. - + Collapse content - + Show content - + Connect @@ -2409,76 +1725,91 @@ Please note, you should add addresses to the list after VPN connection establish PageShare - + OpenVpn native format - + WireGuard native format - + VPN Access - + Connection - + VPN access without the ability to manage the server - + Full access to server - - Server and service - - - - + Server - + Accessing - - Protocols and services - - - - + Connection to - - + + File with connection settings to - + + Save OpenVPN config + + + + + Save WireGuard config + + + + For the AmneziaVPN app - + Full access + + + Servers + + + + + Protocols + + + + + Protocol + + @@ -2486,454 +1817,11 @@ Please note, you should add addresses to the list after VPN connection establish - + Share - - PageShareConnection - - - Share protocol config - - - - - Share for Amnezia - - - - - Share for - - - - - PageShareProtoAmnezia - - - Share for Amnezia - - - - - Anyone who logs in with this code will have the same permissions to use VPN and YOUR SERVER as you. - -This code includes your server credentials! - -Provide this code only to TRUSTED users. - - - - - Anyone who logs in with this code will be able to connect to this VPN server. - -This code does not include server credentials. - -New encryption keys pair will be generated. - - - - - Share - - - - - Save to file - - - - - Save AmneziaVPN config - - - - - Scan QR code using AmneziaVPN mobile - - - - - PageShareProtoCloak - - - Share Cloak Settings - - - - - Note: Cloak protocol using same password for all connections - - - - - Share - - - - - Save to file - - - - - Save AmneziaVPN config - - - - - PageShareProtoIkev2 - - - Share IKEv2 Settings - - - - - - Export p12 certificate - - - - - - Export config for Apple - - - - - - Export config for StrongSwan - - - - - PageShareProtoOpenVPN - - - Share OpenVPN Settings - - - - - New encryption keys pair will be generated. - - - - - Share - - - - - Save to file - - - - - Save OpenVPN config - - - - - PageShareProtoSftp - - - Share SFTP settings - - - - - PageShareProtoShadowSocks - - - Share ShadowSocks Settings - - - - - Note: ShadowSocks protocol using same password for all connections - - - - - Copy config - - - - - Connection string - - - - - Copy string - - - - - PageShareProtoTorWebSite - - - Share Tor Web site - - - - - PageShareProtoWireGuard - - - Share WireGuard Settings - - - - - New encryption keys pair will be generated. - - - - - Share - - - - - Save to file - - - - - Save OpenVPN config - - - - - PageShareProtocolBase - - - Generate config - - - - - Generating config... - - - - - Show config - - - - - PageSites - - - Web site/Hostname/IP address/Subnet - - - - - yousite.com or IP address - - - - - Import IP addresses - - - - - Delete selected - - - - - Select all - - - - - Export all - - - - - PageStart - - - Setup your server to use VPN - - - - - Connect to the already created VPN server - - - - - - Set up your own server - - - - - Import connection - - - - - Connection code - - - - - Connect - - - - - Open file - - - - - Scan QR code - - - - - Restore app config - - - - - How to get own server? → - - - - - Server IP address [:port] - - - - - Login to connect via SSH - - - - - - Password - - - - - - Connect using SSH key - - - - - Private key - - - - - Connect using SSH password - - - - - PageVPN - - - Donate - - - - - Server - - - - - Profile - - - - - Proto - - - - - DNS - - - - - How to use VPN - - - - - For all connections - - - - - Except selected sites - - - - - For selected sites - - - - - + Add site - - - - - PageViewConfig - - - Check config - - - - - Attention! -The config above contains cached OpenVPN connection profile. -AmneziaVPN detected this profile may contain malicious scripts. Please, carefully review the config and import this config only if you completely trust it. - - - - - Suspicious string: - - - - - Cached connection profile: - - - - - Cancel - - - - - Import config - - - PopupType @@ -2942,6 +1830,241 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull + + QKeychain::DeletePasswordJobPrivate + + + Password entry not found + + + + + Could not decrypt data + + + + + + Unknown error + + + + + Could not open wallet: %1; %2 + + + + + Password not found + + + + + Could not open keystore + + + + + Could not remove private key from keystore + + + + + QKeychain::JobPrivate + + + Unknown error + + + + + Access to keychain denied + + + + + QKeychain::PlainTextStore + + + Could not store data in settings: access error + + + + + Could not store data in settings: format error + + + + + Could not delete data from settings: access error + + + + + Could not delete data from settings: format error + + + + + Entry not found + + + + + QKeychain::ReadPasswordJobPrivate + + + Password entry not found + + + + + + Could not decrypt data + + + + + D-Bus is not running + + + + + + Unknown error + + + + + No keychain service available + + + + + Could not open wallet: %1; %2 + + + + + Access to keychain denied + + + + + Could not determine data type: %1; %2 + + + + + + Entry not found + + + + + Unsupported entry type 'Map' + + + + + Unknown kwallet entry type '%1' + + + + + Password not found + + + + + Could not open keystore + + + + + Could not retrieve private key from keystore + + + + + Could not create decryption cipher + + + + + QKeychain::WritePasswordJobPrivate + + + Credential size exceeds maximum size of %1 + + + + + Credential key exceeds maximum size of %1 + + + + + Writing credentials failed: Win32 error code %1 + + + + + Encryption failed + + + + + D-Bus is not running + + + + + + Unknown error + + + + + Could not open wallet: %1; %2 + + + + + Password not found + + + + + Could not open keystore + + + + + Could not create private key generator + + + + + Could not generate new private key + + + + + Could not retrieve private key from keystore + + + + + Could not create encryption cipher + + + + + Could not encrypt data + + + QObject @@ -3155,13 +2278,7 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - - - Web site in Tor network - - - - + DNS Service @@ -3170,62 +2287,150 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull Sftp file sharing service + + + + Website in Tor network + + Amnezia DNS - - - OpenVPN container - - - Container with OpenVpn and ShadowSocks + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - - Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. - - WireGuard container - - - - - IPsec container + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + + + + + Deploy a WordPress site on the Tor network in two clicks. + + + + + Replace the current DNS server with your own. This will increase your privacy level. + + + + + Creates a file vault on your server to securely store and transfer files. + + + + + OpenVPN container + + + + + Container with OpenVpn and ShadowSocks + + + + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + + + + + WireGuard container + + + + + IPsec container + + + + Sftp file sharing service - is secure FTP service - + Sftp service - - An error occurred while saving the list of clients. - - - - - SelectContainer - - - VPN containers + + Entry not found - - Other containers + + Access to keychain denied + + + + + No keyring daemon + + + + + Already unlocked + + + + + No such keyring + + + + + Bad arguments + + + + + I/O error + + + + + Cancelled + + + + + Keyring already exists + + + + + No match + + + + + Unknown error + + + + + error 0x%1: %2 @@ -3237,62 +2442,6 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - - ServerConfiguringProgressLogic - - - - Please wait, configuring process may take up to 5 minutes - - - - - Configuring... - - - - - Operation finished - - - - - ServerContainersLogic - - - Error occurred while configuring server. - - - - - Error message: - - - - - See logs for details. - - - - - ServerSettingsLogic - - - - Clear client cached profile - - - - - Service: - - - - - Cache cleared - - - Settings @@ -3310,212 +2459,144 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull SettingsController - + Software version - - Save log + + All settings have been reset to default values - - Backup application config + + Cached profiles cleared - - Open backup - - - - - Backup file is empty - - - - + Backup file is corrupted - - ShareConnectionButtonCopyType - - - Copy - - - - - Copied - - - ShareConnectionDrawer - + + + Save AmneziaVPN config + + + + Share - + Copy - + + Copied + + + + Show content - - To read the QR code in the Amnezia app, select "Add Server" → "I have connection details" + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - ShareConnectionLogic + SitesController - - Error while generating connection profile + + Hostname not look like ip adress or domain name - - Error occurred while generating the config. + + New site added: %1 - - Error message: + + Site removed: %1 - - See logs for details. - - - - - SitesLogic - - - These sites will be opened using VPN + + Can't open file: %1 - - These sites will be excepted from VPN - - - - - StartPageLogic - - - - Connect + + Failed to parse JSON data from file: %1 - - - Please fill in all fields + + The JSON data is not an array in file: - - Connecting... + + Import completed - - Open config file + + Export completed SystemTrayNotificationHandler - + + Show - + + Connect - + + Disconnect - + + Visit Website - + + Quit - - UiLogic - - - Error occurred while configuring server. - - - - - Error message: - - - - - See logs for details. - - - VpnConnection - + Mbps - - VpnLogic - - - - 0 Mbps - - - - - AmneziaVPN not supporting selected protocol on this device. Select another protocol. - - - - - VPN Protocols is not installed. - Please install VPN container at first - - - - - VPN Protocol not chosen - - - VpnProtocol @@ -3562,56 +2643,46 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull amnezia::ContainerProps - + Low - + High - + Medium - + Many foreign websites and VPN providers are blocked - + Some foreign sites are blocked, but VPN providers are not blocked - + I just want to increase the level of privacy - main + main2 - - It's public key. Private key required + + Private key passphrase - - Ssh log - - - - - App log - - - - - Wrap words + + Save diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts new file mode 100644 index 000000000..37e277862 --- /dev/null +++ b/client/translations/amneziavpn_zh_CN.ts @@ -0,0 +1,2725 @@ + + + + + AmneziaApplication + + + Split tunneling for WireGuard is not implemented, the option was disabled + 未启用选项,还未实现基于WireGuard协议的VPN分流 + + + + AndroidController + + + AmneziaVPN + + + + + VPN Connected + Refers to the app - which is currently running the background and waiting + VPN已连接 + + + + ConnectionController + + + + + + Connect + 连接 + + + + VPN Protocols is not installed. + Please install VPN container at first + 不存在VPN协议,请先安装 + + + + Connection... + 连接中 + + + + Connected + 已连接 + + + + Reconnection... + 重连中 + + + + Disconnection... + 断开中 + + + + Settings updated successfully, Reconnnection... + 配置已更新,重连中 + + + + ConnectionTypeSelectionDrawer + + + Connection data + 连接数据 + + + + Server IP, login and password + 服务器IP,用户名和密码 + + + + QR code, key or configuration file + 二维码,授权码或者配置文件 + + + + ContextMenuType + + + C&ut + 剪切 + + + + &Copy + 拷贝 + + + + &Paste + 粘贴 + + + + &SelectAll + 全选 + + + + ExportController + + + Access error! + 访问错误 + + + + HomeContainersListView + + + The selected protocol is not supported on the current platform + 当前平台不支持所选协议 + + + + Reconnect via VPN Procotol: + 重连基于VPN协议: + + + + ImportController + + + Scanned %1 of %2. + 扫描 %1 of %2. + + + + InstallController + + installed successfully. + 安装成功 + + + is already installed on the server. + 已安装在服务器上 + + + + + %1 installed successfully. + %1 安装成功。 + + + + + %1 is already installed on the server. + 服务器上已经安装 %1。 + + + + + +Already installed containers were found on the server. All installed containers have been added to the application + +在服务上发现已经安装协议并添加到应用程序 + + + + Settings updated successfully + 配置更新成功 + + + + Server '%1' was removed + 已移除服务器 '%1' + + + + All containers from server '%1' have been removed + 服务器 '%1' 的所有容器已移除 + + + + %1 has been removed from the server '%2' + %1 已从服务器 '%2' 上移除 + + + 1% has been removed from the server '%2' + %1 已从服务器 '%2' 上移除 + + + Server ' + 服务器 + + + ' was removed + 已经移除 + + + has been removed from the server ' + 协议已从 + + + + Please login as the user + 请以用户身份登录 + + + + Server added successfully + 服务器添加成功 + + + + KeyChainClass + + + Read key failed: %1 + 获取授权码失败: %1 + + + + Write key failed: %1 + 写入授权码失败: %1 + + + + Delete key failed: %1 + 删除授权码失败: %1 + + + + NotificationHandler + + + + AmneziaVPN + + + + + VPN Connected + 已连接到VPN + + + + VPN Disconnected + 已从VPN断开 + + + + AmneziaVPN notification + AmneziaVPN 提示 + + + + Unsecured network detected: + 发现不安全网络 + + + + PageDeinstalling + + + Removing services from %1 + 正从 %1 移除服务 + + + + Usually it takes no more than 5 minutes + 通常5分钟之内完成 + + + + PageHome + + + VPN protocol + VPN协议 + + + + Servers + 服务器 + + + + PageProtocolCloakSettings + + + Cloak settings + Cloak 配置 + + + + Disguised as traffic from + 伪装流量来自 + + + + Port + 端口 + + + + + Cipher + 解码 + + + + Save and Restart Amnezia + 保存并重启Amnezia + + + + PageProtocolOpenVpnSettings + + + OpenVPN settings + OpenVPN 配置 + + + + VPN Addresses Subnet + VPN子网掩码 + + + + Network protocol + 网络协议 + + + + Port + 端口 + + + + Auto-negotiate encryption + 自动协商加密 + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + Cipher + 解码 + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + TLS auth + TLS认证 + + + + Block DNS requests outside of VPN + 阻止VPN外的DNS请求 + + + + Additional client configuration commands + 附加客户端配置命令 + + + + + Commands: + 命令: + + + + Additional server configuration commands + 附加服务器端配置命令 + + + + Remove OpenVPN + 移除OpenVPN + + + + Remove OpenVpn from server? + 从服务器移除OpenVPN吗? + + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Save and Restart Amnezia + 保存并重启Amnezia + + + + PageProtocolRaw + + + settings + 配置 + + + + Show connection options + 展示连接选项 + + + + Connection options + 连接选项 + + + + Remove + 移除 + + + + Remove %1 from server? + 从服务器移除 %1 ? + + + from server? + 从服务器 + + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageProtocolShadowSocksSettings + + + ShadowSocks settings + ShadowSocks 配置 + + + + Port + 端口 + + + + + Cipher + 解码 + + + + Save and Restart Amnezia + 保存并重启Amnezia + + + + PageServiceDnsSettings + + + A DNS service is installed on your server, and it is only accessible via VPN. + + 您的服务器上安装了DNS服务,并且只能通过VPN访问。 + + + + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. + DNS地址与您的服务器地址相同。您可以在连接选项卡下的设置中配置 DNS + + + + Remove + 移除 + + + + Remove %1 from server? + 从服务器移除 %1 ? + + + from server? + 从服务器 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageServiceSftpSettings + + + Settings updated successfully + 配置更新成功 + + + + SFTP settings + SFTP 配置 + + + + Host + 主机 + + + + + + + Copied + 拷贝 + + + + Port + 端口 + + + + Login + 用户 + + + + Password + 密码 + + + + Mount folder on device + 在设备上挂载文件夹 + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + 要将远程 SFTP 文件夹安装为本地驱动器,请执行以下步骤: <br> + + + + + <br>1. Install the latest version of + <br>1. 安装最新版的 + + + + + <br>2. Install the latest version of + <br>2. 安装最新版的 + + + + Detailed instructions + 详细说明 + + + + Remove SFTP and all data stored there + 移除SFTP和其本地所有数据 + + + + Remove SFTP and all data stored there? + 移除SFTP和其本地所有数据? + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + 配置更新成功 + + + + Tor website settings + Tor网站配置 + + + + Website address + 网址 + + + + Copied + 拷贝 + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. + 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 + + + + After installation it takes several minutes while your onion site will become available in the Tor Network. + 安装几分钟后,洋葱站点才会在 Tor 网络中生效。 + + + + When configuring WordPress set the domain as this onion address. + 配置 WordPress 时,将域设置为此洋葱地址。 + + + + Remove website + 移除网站 + + + + The site with all data will be removed from the tor network. + 网站及其所有数据将从 Tor 网络中删除 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageSettings + + + Settings + 设置 + + + + Servers + 服务器 + + + + Connection + 连接 + + + + Application + 应用 + + + + Backup + 备份 + + + + About AmneziaVPN + 关于 + + + + PageSettingsAbout + + + Support the project with a donation + 捐款 + + + + This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app. + 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 +如果您不喜欢,请捐助支持我们改进它。 + + + + Card on Patreon + Patreon订阅 + + + + https://www.patreon.com/amneziavpn + + + + + Show other methods on Github + 其他捐款途径 + + + + Contacts + 联系方式 + + + + Telegram group + 电报群 + + + + To discuss features + 用于功能讨论 + + + + https://t.me/amnezia_vpn_en + + + + + Mail + 邮件 + + + + For reviews and bug reports + 用于评论和提交软件的缺陷 + + + + Github + + + + + https://github.com/amnezia-vpn/amnezia-client + + + + + Website + 官网 + + + + https://amnezia.org + + + + + Check for updates + 更新 + + + + PageSettingsApplication + + + Application + 应用 + + + + Auto start + 自动运行 + + + + Launch the application every time + 总是在系统 + + + + starts + 启动时自动运行运用程序 + + + + Start minimized + 最小化 + + + + Launch application minimized + 开启应用程序时窗口最小化 + + + + Language + 语言 + + + + Logging + 日志 + + + + Enabled + 开启 + + + + Disabled + 禁用 + + + + Reset settings and remove all data from the application + 重置并清理应用的所有数据 + + + + Reset settings and remove all data from the application? + 重置并清理应用的所有数据? + + + + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. + 所有配置恢复为默认值。在服务器上保留所有已安装的AmneziaVPN服务。 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageSettingsBackup + + + Settings restored from backup file + 从备份文件还原配置 + + + + Backup + 备份 + + + + Configuration backup + 配置备份 + + + + It will help you instantly restore connection settings at the next installation + 帮助您在下次安装时立即恢复连接设置 + + + + Make a backup + 进行备份 + + + + Save backup file + 保存备份 + + + + + Backup files (*.backup) + + + + + Restore from backup + 从备份还原 + + + + Open backup file + 打开备份文件 + + + + Import settings from a backup file? + 从备份文件导入设置? + + + + All current settings will be reset + 当前所有设置将重置 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageSettingsConnection + + + Connection + 连接 + + + + Auto connect + 自动连接 + + + + Connect to VPN on app start + 应用开启时连接VPN + + + Use AmneziaDNS if installed on the server + 使用AmneziaDNS,如其已安装在服务器上 + + + + Use AmneziaDNS + 使用AmneziaDNS + + + + If AmneziaDNS is installed on the server + 如其已安装至服务器上 + + + + DNS servers + DNS服务器列表 + + + + If AmneziaDNS is not used or installed + 如果未使用或未安装AmneziaDNS + + + + Split site tunneling + 网站级VPN分流 + + + + Allows you to connect to some sites through a secure connection, and to others bypassing it + 使用VPN访问指定网站,其他的则绕过 + + + + Separate application tunneling + 应用级VPN分流 + + + + Allows you to use the VPN only for certain applications + 仅限指定应用使用VPN + + + + PageSettingsDns + + + DNS servers + DNS服务器 + + + + If AmneziaDNS is not used or installed + 如果未使用或未安装Amnezia DNS + + + + Primary DNS + 首选 DNS + + + + Secondary DNS + 备用 DNS + + + + Restore default + 恢复默认配置 + + + + Restore default DNS settings? + 是否恢复默认DNS配置? + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Settings have been reset + 已重置 + + + + Save + 保存 + + + + Settings saved + 配置已保存 + + + + PageSettingsLogging + + + Logging + 日志 + + + + Save logs + 记录日志 + + + + Open folder with logs + 打开日志文件夹 + + + + Save + 保存 + + + + Logs files (*.log) + + + + + Save logs to file + 保存日志到文件 + + + + Clear logs? + 清除日志? + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Logs have been cleaned up + 已清理日志 + + + + Clear logs + 清理日志 + + + + PageSettingsServerData + + + All installed containers have been added to the application + 所有已安装的容器已添加到应用程序中 + + + + No new installed containers found + 未找到新安装的容器 + + + + Clear Amnezia cache + 清除 Amnezia 缓存 + + + + May be needed when changing other settings + 更改其他设置时可能需要 + + + + Clear cached profiles? + 清除缓存的配置文件? + + + + + + + + + + + Continue + 继续 + + + + + + Cancel + 取消 + + + + Check the server for previously installed Amnezia services + 检查服务器上是否存在 Amnezia 服务 + + + + Add them to the application if they were not displayed + 如果存在且未被显示,则添加到应用程序里 + + + + Remove server from application + 移除本地服务器信息 + + + + Remove server? + 移除本地服务器信息? + + + + All installed AmneziaVPN services will still remain on the server. + 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 + + + + Clear server from Amnezia software + 移除Amnezia中服务器信息 + + + + Clear server from Amnezia software? + 从Amnezia中清除服务器? + + + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + 服务器上的所有容器都将被删除。这意味着配置文件、密钥和证书将被删除。 + + + + PageSettingsServerInfo + + + Server name + 服务器名称 + + + + Save + 保存 + + + + Protocols + 协议 + + + + Services + 服务 + + + + Data + 数据 + + + + PageSettingsServerProtocol + + + settings + 配置 + + + + Remove + 移除 + + + from server? + 从服务器 + + + + Remove %1 from server? + 从服务器移除 %1 ? + + + + All users with whom you shared a connection will no longer be able to connect to it + 与您共享连接的所有用户将无法再连接到此链接 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + PageSettingsServersList + + + Servers + 服务器 + + + + PageSettingsSplitTunneling + + + Only the addresses in the list must be opened via VPN + 仅列表中的地址须通过VPN访问 + + + + Addresses from the list should never be opened via VPN + 勿通过VPN访问列表中的地址 + + + + Split site tunneling + 网站级VPN分流 + + + + Mode + 方式 + + + + Remove + 移除 + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Site or IP + 网址或IP地址 + + + + Import/Export Sites + 导入/导出网址 + + + + Import + 导入 + + + + Save site list + 保存网址 + + + + Save sites + 保存网址 + + + + + + Sites files (*.json) + + + + + Import a list of sites + 导入网址列表 + + + + Replace site list + 替换网址列表 + + + + + Open sites file + 打开网址文件 + + + + Add imported sites to existing ones + 将导入的网址添加到现有网址中 + + + + PageSetupWizardConfigSource + + + Server connection + 服务器连接 + + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay as long as it's from someone you trust. + 请勿使用公共来源的连接代码。它可能是为了拦截您的数据而创建的。 +最好是来源可信。 + + + + What do you have? + + + + + File with connection settings or backup + 包含连接配置或备份的文件 + + + + File with connection settings + 包含连接配置的文件 + + + + Open config file + 打开配置文件 + + + + QR-code + 二维码 + + + + Key as text + 授权码文本 + + + + PageSetupWizardCredentials + + + Server connection + 服务器连接 + + + + Server IP address [:port] + 服务器IP [:端口] + + + + 255.255.255.255:88 + + + + + Login to connect via SSH + 用户名 + + + + Password / SSH private key + 密码 或者 私钥 + + + + Continue + 继续 + + + + Ip address cannot be empty + IP不能为空 + + + + Enter the address in the format 255.255.255.255:88 + 按照这种格式输入 255.255.255.255:88 + + + + Login cannot be empty + 用户名不能为空 + + + + Password/private key cannot be empty + 密码或者私钥不能为空 + + + + PageSetupWizardEasy + + + What is the level of internet control in your region? + 您所在地区的互联网控制力度如何? + + + + Set up a VPN yourself + 自己架设VPN + + + + I want to choose a VPN protocol + 我想选择VPN协议 + + + + Continue + 继续 + + + + Set up later + 稍后设置 + + + + PageSetupWizardInstalling + + + + Usually it takes no more than 5 minutes + 通常不超过5分钟 + + + + The server has already been added to the application + 服务器已添加到应用程序中 + + + + Amnesia has detected that your server is currently + Amnezia 检测到您的服务器当前 + + + + busy installing other software. Amnesia installation + 正安装其他软件。Amnezia安装 + + + + will pause until the server finishes installing other software + 将暂停,直到服务器完成安装其他软件。 + + + + Installing + 安装中 + + + + PageSetupWizardProtocolSettings + + + Installing %1 + 正在安装 %1 + + + + More detailed + 更多细节 + + + + Close + 关闭 + + + + Network protocol + 网络协议 + + + + Port + 端口 + + + + Install + 安装 + + + + PageSetupWizardProtocols + + + VPN protocol + VPN 协议 + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + 选择最适合您的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. + 将相机对准二维码并按住几秒钟 + + + + PageSetupWizardStart + + + Settings restored from backup file + 从备份文件还原配置 + + + + Free service for creating a personal VPN on your server. + + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + + + + + I have the data to connect + + + + + I have nothing + + + + + PageSetupWizardTextKey + + + Connection key + 连接授权码 + + + + A line that starts with vpn://... + 以 vpn://... 开始的行 + + + + Key + 授权码 + + + + Insert + 插入 + + + + Continue + 继续 + + + + PageSetupWizardViewConfig + + + New connection + 新连接 + + + + Do not use connection code from public sources. It could be created to intercept your data. + 请勿使用公共来源的连接代码。它可以被创建来拦截您的数据。 + + + + Collapse content + + + + + Show content + 展示内容 + + + + Connect + 连接 + + + + PageShare + + + Save OpenVPN config + 保存OpenVPN配置 + + + + Save WireGuard config + 保存WireGuard配置 + + + + For the AmneziaVPN app + AmneziaVPN 应用 + + + + OpenVpn native format + OpenVPN原生格式 + + + + WireGuard native format + WireGuard原生格式 + + + + VPN Access + 访问VPN + + + + Connection + 连接 + + + + Full access + 完整授权 + + + + VPN access without the ability to manage the server + 无权控制服务器 + + + + Full access to server + 获得服务器完整授权 + + + + Servers + 服务器 + + + + Server + 服务器 + + + + Accessing + 访问 + + + + + File with connection settings to + 连接配置文件的内容为: + + + + Protocols + 协议 + + + + Protocol + 协议 + + + + Connection to + 连接到 + + + + + Connection format + 连接方式 + + + + Share + 共享 + + + + PopupType + + + Close + 关闭 + + + + QKeychain::DeletePasswordJobPrivate + + + Password entry not found + 没有密码输入 + + + + Could not decrypt data + 不能加密数据 + + + + + Unknown error + 位置错误 + + + + Could not open wallet: %1; %2 + 无法打开钱包: %1; %2 + + + + Password not found + 未发现密码 + + + + Could not open keystore + 无法打开密钥库 + + + + Could not remove private key from keystore + 无法从密钥库中删除私钥 + + + + QKeychain::JobPrivate + + + Unknown error + 未知错误 + + + + Access to keychain denied + 访问钥匙串被拒绝 + + + + QKeychain::PlainTextStore + + + Could not store data in settings: access error + 无法在配置中存储数据:访问错误 + + + + Could not store data in settings: format error + 无法在陪置中存储数据:格式错误 + + + + Could not delete data from settings: access error + 无法在配置中删除数据:访问错误 + + + + Could not delete data from settings: format error + 无法在配置中删除数据:格式错误 + + + + Entry not found + 未找到条目 + + + + QKeychain::ReadPasswordJobPrivate + + + Password entry not found + 没有密码输入 + + + + + Could not decrypt data + 不能加密数据 + + + + D-Bus is not running + + + + + + Unknown error + + + + + No keychain service available + + + + + Could not open wallet: %1; %2 + 无法打开钱包: %1; %2 + + + + Access to keychain denied + 访问钥匙串被拒绝 + + + + Could not determine data type: %1; %2 + + + + + + Entry not found + + + + + Unsupported entry type 'Map' + + + + + Unknown kwallet entry type '%1' + + + + + Password not found + 未发现密码 + + + + Could not open keystore + 无法打开密钥库 + + + + Could not retrieve private key from keystore + 无法从密钥存储库中检索私钥 + + + + Could not create decryption cipher + 无法创建解密密码 + + + + QKeychain::WritePasswordJobPrivate + + + Credential size exceeds maximum size of %1 + + + + + Credential key exceeds maximum size of %1 + + + + + Writing credentials failed: Win32 error code %1 + + + + + Encryption failed + + + + + D-Bus is not running + + + + + + Unknown error + + + + + Could not open wallet: %1; %2 + 无法打开钱包: %1; %2 + + + + Password not found + 未发现密码 + + + + Could not open keystore + 无法打开密钥库 + + + + Could not create private key generator + 无法创建私钥生成器 + + + + Could not generate new private key + 无法生成新的私钥 + + + + Could not retrieve private key from keystore + 无法从密钥库检索私钥 + + + + Could not create encryption cipher + 无法创建加密密码 + + + + Could not encrypt data + 无法加密数据 + + + + QObject + + + Sftp service + Sftp 服务 + + + + No error + 没有错误 + + + + Unknown Error + 位置错误 + + + + Function not implemented + 功能未实现 + + + + Server check failed + 服务器检测失败 + + + + Server port already used. Check for another software + 检测服务器该端口是否被其他软件被占用 + + + + Server error: Docker container missing + Server error: Docker容器丢失 + + + + Server error: Docker failed + Server error: Docker失败 + + + + Installation canceled by user + 用户取消安装 + + + + The user does not have permission to use sudo + 用户没有root权限 + + + + Ssh request was denied + ssh请求被拒绝 + + + + Ssh request was interrupted + ssh请求中断 + + + + Ssh internal error + ssh内部错误 + + + + Invalid private key or invalid passphrase entered + 输入的私钥或密码无效 + + + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 + + + + Timeout connecting to server + 连接服务器超时 + + + + Sftp error: End-of-file encountered + Sftp错误: 遇到文件结尾 + + + + Sftp error: File does not exist + Sftp错误: 文件不存在 + + + + Sftp error: Permission denied + Sftp错误: 权限受限 + + + + Sftp error: Generic failure + Sftp错误: 一般失败 + + + + Sftp error: Garbage received from server + Sftp错误: 从服务器收到垃圾信息 + + + + Sftp error: No connection has been set up + + + + + Sftp error: There was a connection, but we lost it + + + + + Sftp error: Operation not supported by libssh yet + + + + + Sftp error: Invalid file handle + + + + + Sftp error: No such file or directory path exists + + + + + Sftp error: An attempt to create an already existing file or directory has been made + + + + + Sftp error: Write-protected filesystem + + + + + Sftp error: No media was in remote drive + + + + + Failed to save config to disk + 配置保存到磁盘失败 + + + + OpenVPN config missing + OpenVPN配置丢失 + + + + OpenVPN management server error + OpenVPN 管理服务器错误 + + + + OpenVPN executable missing + OpenVPN 可执行文件丢失 + + + + ShadowSocks (ss-local) executable missing + ShadowSocks (ss-local) 执行文件丢失 + + + + Cloak (ck-client) executable missing + Cloak (ck-client) 执行文件丢失 + + + + Amnezia helper service error + Amnezia 帮助服务错误 + + + + OpenSSL failed + OpenSSL失败 + + + + Can't connect: another VPN connection is active + 无法连接:另一个VPN连接处于活动状态 + + + + Can't setup OpenVPN TAP network adapter + 无法设置 OpenVPN TAP 网络适配器 + + + + VPN pool error: no available addresses + VPN 池错误:没有可用地址 + + + + The config does not contain any containers and credentiaks for connecting to the server + 该配置不包含任何用于连接到服务器的容器和凭据。 + + + + Internal error + 内部错误 + + + + IPsec + + + + + + Website in Tor network + 在 Tor 网络中架设网站 + + + + Amnezia DNS + + + + + Sftp file sharing service + SFTP文件共享服务 + + + + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. + OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 + + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. + ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 + + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 + + + + Deploy a WordPress site on the Tor network in two clicks. + 只需点击两次即可架设 WordPress 网站到 Tor 网络 + + + + Replace the current DNS server with your own. This will increase your privacy level. + 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私级别。 + + + + Creates a file vault on your server to securely store and transfer files. + 在您的服务器上创建文件库以安全地存储和传输文件 + + + + OpenVPN container + OpenVPN容器 + + + + Container with OpenVpn and ShadowSocks + 带有 OpenVpn 和 ShadowSocks 的容器 + + + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + 具有 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 + + + + WireGuard container + WireGuard 容器 + + + + IPsec container + IPsec 容器 + + + + DNS Service + DNS 服务 + + + + Sftp file sharing service - is secure FTP service + Sftp 文件共享服务 - 安全的 FTP 服务 + + + + Entry not found + 未找到记录 + + + + Access to keychain denied + 访问钥匙串被拒绝 + + + + No keyring daemon + 没有密钥环守护进程 + + + + Already unlocked + 已经解锁 + + + + No such keyring + 没有这样的密钥环 + + + + Bad arguments + 错误参数 + + + + I/O error + I/O错误 + + + + Cancelled + 已取消 + + + + Keyring already exists + 密匙环已经存在 + + + + No match + 不匹配 + + + + Unknown error + 未知错误 + + + + error 0x%1: %2 + 错误 0x%1: %2 + + + + SelectLanguageDrawer + + + Choose language + 选择语言 + + + + Settings + + + Server #1 + + + + + + Server + 服务器 + + + + SettingsController + + + Software version + 软件版本 + + + + Backup file is corrupted + 备份文件已损坏 + + + + All settings have been reset to default values + 所配置恢复为默认值 + + + + Cached profiles cleared + 缓存的配置文件已清除 + + + + ShareConnectionDrawer + + + + Save AmneziaVPN config + 保存配置 + + + + Share + 共享 + + + + Copy + 拷贝 + + + + Copied + 已拷贝 + + + + Show content + 展示内容 + + + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" + 要读取 Amnezia 应用程序中的二维码,请选择“添加服务器”→“我有数据要连接”→“二维码、密钥或配置文件” + + + + SitesController + + + Hostname not look like ip adress or domain name + + + + + New site added: %1 + + + + + Site removed: %1 + + + + + Can't open file: %1 + + + + + Failed to parse JSON data from file: %1 + + + + + The JSON data is not an array in file: + + + + + Import completed + + + + + Export completed + + + + + SystemTrayNotificationHandler + + + + Show + 界面 + + + + + Connect + 连接 + + + + + Disconnect + 断开 + + + + + Visit Website + 官网 + + + + + Quit + 退出 + + + + VpnConnection + + + Mbps + + + + + VpnProtocol + + + Unknown + 未知 + + + + Disconnected + 断开连接 + + + + Preparing + 准备中 + + + + Connecting... + 连接中 + + + + Connected + 已连接 + + + + Disconnecting... + 断开中 + + + + Reconnecting... + 重连中 + + + + Error + 错误 + + + + amnezia::ContainerProps + + + Low + + + + + High + + + + + Medium + + + + + I just want to increase the level of privacy + 我只是想提高隐私级别 + + + + Many foreign websites and VPN providers are blocked + 大多国外网站和VPN提供商被屏蔽 + + + + Some foreign sites are blocked, but VPN providers are not blocked + 一些国外网站被屏蔽,但VPN提供商未被屏蔽 + + + + main2 + + + Private key passphrase + 私钥密码 + + + + Save + 保存 + + + diff --git a/client/translations/translations.qrc.in b/client/translations/translations.qrc.in new file mode 100644 index 000000000..f49df6615 --- /dev/null +++ b/client/translations/translations.qrc.in @@ -0,0 +1,5 @@ + + + @QM_FILE_LIST@ + + diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 77ca0f5f9..74438dcce 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -19,23 +19,8 @@ ConnectionController::ConnectionController(const QSharedPointer &s Qt::QueuedConnection); connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); -} -ConnectionController::~ConnectionController() -{ -// todo use ConnectionController instead of using m_vpnConnection directly -#ifdef AMNEZIA_DESKTOP - if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { - m_vpnConnection->disconnectFromVpn(); - for (int i = 0; i < 50; i++) { - qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - QThread::msleep(100); - if (m_vpnConnection->isDisconnected()) { - break; - } - } - } -#endif + m_state = Vpn::ConnectionState::Disconnected; } void ConnectionController::openConnection() @@ -70,6 +55,8 @@ QString ConnectionController::getLastConnectionError() void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) { + m_state = state; + m_isConnected = false; m_connectionStateText = tr("Connection..."); switch (state) { @@ -126,6 +113,17 @@ void ConnectionController::onCurrentContainerUpdated() } } +void ConnectionController::onTranslationsUpdated() +{ + // get translated text of current state + onConnectionStateChanged(getCurrentConnectionState()); +} + +Vpn::ConnectionState ConnectionController::getCurrentConnectionState() +{ + return m_state; +} + QString ConnectionController::connectionStateText() const { return m_connectionStateText; diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 7bfe0faca..74a3f6003 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -19,7 +19,7 @@ public: const QSharedPointer &containersModel, const QSharedPointer &vpnConnection, QObject *parent = nullptr); - ~ConnectionController(); + ~ConnectionController() = default; bool isConnected() const; bool isConnectionInProgress() const; @@ -34,6 +34,8 @@ public slots: void onCurrentContainerUpdated(); + void onTranslationsUpdated(); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); @@ -44,6 +46,8 @@ signals: void reconnectWithUpdatedContainer(const QString &message); private: + Vpn::ConnectionState getCurrentConnectionState(); + QSharedPointer m_serversModel; QSharedPointer m_containersModel; @@ -52,6 +56,8 @@ private: bool m_isConnected = false; bool m_isConnectionInProgress = false; QString m_connectionStateText = tr("Connect"); + + Vpn::ConnectionState m_state; }; #endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 5a060cf10..08b662ec8 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -61,15 +61,6 @@ ImportController::ImportController(const QSharedPointer &serversMo { #ifdef Q_OS_ANDROID mInstance = this; - // Set security screen for Android app - AndroidUtils::runOnAndroidThreadSync([]() { - QJniObject activity = AndroidUtils::getActivity(); - QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()) { - const int FLAG_SECURE = 8192; - window.callMethod("addFlags", "(I)V", FLAG_SECURE); - } - }); AndroidUtils::runOnAndroidThreadAsync([]() { JNINativeMethod methods[] { diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 63510d1a0..72cc34b60 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -107,14 +107,12 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); installedContainers.insert(container, config); - finishMessage = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); } else { - finishMessage = - ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); + finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); } if (installedContainers.size() > 1) { - finishMessage += tr("\nAlready installed containers were found on the server. " - "All installed containers have been added to the application"); + finishMessage += tr("\nAdded containers that were already installed on the server"); } if (errorCode == ErrorCode::NoError) { @@ -160,10 +158,9 @@ void InstallController::installContainer(DockerContainer container, QJsonObject if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(serverCredentials, container, config); installedContainers.insert(container, config); - finishMessage = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); } else { - finishMessage = - ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); + finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); } bool isInstalledContainerAddedToGui = false; @@ -278,7 +275,7 @@ void InstallController::removeCurrentlyProcessedServer() QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); m_serversModel->removeServer(); - emit removeCurrentlyProcessedServerFinished(tr("Server '") + serverName + tr("' was removed")); + emit removeCurrentlyProcessedServerFinished(tr("Server '%1' was removed").arg(serverName)); } void InstallController::removeAllContainers() @@ -288,7 +285,7 @@ void InstallController::removeAllContainers() ErrorCode errorCode = m_containersModel->removeAllContainers(); if (errorCode == ErrorCode::NoError) { - emit removeAllContainersFinished(tr("All containers from server '") + serverName + ("' have been removed")); + emit removeAllContainersFinished(tr("All containers from server '%1' have been removed").arg(serverName)); return; } emit installationErrorOccurred(errorString(errorCode)); @@ -304,8 +301,8 @@ void InstallController::removeCurrentlyProcessedContainer() ErrorCode errorCode = m_containersModel->removeCurrentlyProcessedContainer(); if (errorCode == ErrorCode::NoError) { - emit removeCurrentlyProcessedContainerFinished(containerName + tr(" has been removed from the server '") - + serverName + "'"); + + emit removeCurrentlyProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName).arg(serverName)); return; } emit installationErrorOccurred(errorString(errorCode)); diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 7b8f74ab4..cb500618c 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -37,6 +37,8 @@ PageController::PageController(const QSharedPointer &serversModel, connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); }); connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); }); #endif + + m_isTriggeredByConnectButton = false; } QString PageController::getInitialPage() @@ -145,3 +147,13 @@ void PageController::drawerClose() m_drawerLayer = 0; } } + +bool PageController::isTriggeredByConnectButton() +{ + return m_isTriggeredByConnectButton; +} + +void PageController::setTriggeredBtConnectButton(bool trigger) +{ + m_isTriggeredByConnectButton = trigger; +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f1de02f3a..bf282a31e 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -85,6 +85,10 @@ public slots: void drawerOpen(); void drawerClose(); + + bool isTriggeredByConnectButton(); + void setTriggeredBtConnectButton(bool trigger); + signals: void goToPage(PageLoader::PageEnum page, bool slide = true); void goToStartPage(); @@ -121,6 +125,8 @@ private: PageLoader::PageEnum m_currentRootPage; int m_drawerLayer; + + bool m_isTriggeredByConnectButton; }; #endif // PAGECONTROLLER_H diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 46993f6a5..3edfe3d99 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -6,6 +6,11 @@ #include "systemController.h" #include "ui/qautostart.h" #include "version.h" +#ifdef Q_OS_ANDROID + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include +#endif SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -18,6 +23,20 @@ SettingsController::SettingsController(const QSharedPointer &serve m_settings(settings) { m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); + +#ifdef Q_OS_ANDROID + if (!m_settings->isScreenshotsEnabled()) { + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod("addFlags", "(I)V", FLAG_SECURE); + } + }); + } +#endif } void SettingsController::toggleAmneziaDns(bool enable) @@ -152,3 +171,26 @@ void SettingsController::toggleStartMinimized(bool enable) { m_settings->setStartMinimized(enable); } + +bool SettingsController::isScreenshotsEnabled() +{ + return m_settings->isScreenshotsEnabled(); +} + +void SettingsController::toggleScreenshotsEnabled(bool enable) +{ + m_settings->setScreenshotsEnabled(enable); +#ifdef Q_OS_ANDROID + std::string command = enable ? "clearFlags" : "addFlags"; + + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([&command]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod(command.c_str(), "(I)V", FLAG_SECURE); + } + }); +#endif +} \ No newline at end of file diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 5a51a3b4b..be041f3ed 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -54,6 +54,9 @@ public slots: bool isStartMinimizedEnabled(); void toggleStartMinimized(bool enable); + bool isScreenshotsEnabled(); + void toggleScreenshotsEnabled(bool enable); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index a27e91d05..4d0391be1 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -64,7 +64,7 @@ void SitesController::addSite(QString hostname) QHostInfo::lookupHost(hostname, this, resolveCallback); } - emit finished(tr("New site added: ") + hostname); + emit finished(tr("New site added: %1").arg(hostname)); } void SitesController::removeSite(int index) @@ -77,7 +77,7 @@ void SitesController::removeSite(int index) Q_ARG(QStringList, QStringList() << hostname)); QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); - emit finished(tr("Site removed: ") + hostname); + emit finished(tr("Site removed: %1").arg(hostname)); } void SitesController::importSites(const QString &fileName, bool replaceExisting) @@ -85,19 +85,19 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting) QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { - emit errorOccurred(tr("Can't open file: ") + fileName); + emit errorOccurred(tr("Can't open file: %1").arg(fileName)); return; } QByteArray jsonData = file.readAll(); QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); if (jsonDocument.isNull()) { - emit errorOccurred(tr("Failed to parse JSON data from file: ") + fileName); + emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName)); return; } if (!jsonDocument.isArray()) { - emit errorOccurred(tr("The JSON data is not an array in file: ") + fileName); + emit errorOccurred(tr("The JSON data is not an array in file: ").arg(fileName)); return; } diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index afff44b6d..6cf855a65 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -214,6 +214,20 @@ bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) return containers.contains(DockerContainer::Dns); } +bool ContainersModel::isAnyContainerInstalled() +{ + for (int row=0; row < rowCount(); row++) { + QModelIndex idx = this->index(row, 0); + + if (this->data(idx, IsInstalledRole).toBool() && + this->data(idx, ServiceTypeRole).toInt() == ServiceType::Vpn) { + return true; + } + } + + return false; +} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 741a0620b..2cc41cbfe 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -63,7 +63,7 @@ public slots: bool isAmneziaDnsContainerInstalled(); bool isAmneziaDnsContainerInstalled(const int serverIndex); - // bool isOnlyServicesInstalled(const int serverIndex); + bool isAnyContainerInstalled(); protected: QHash roleNames() const override; diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index 5135f3483..b860b9daf 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -6,7 +6,8 @@ LanguageModel::LanguageModel(std::shared_ptr settings, QObject *parent QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); i++) { m_availableLanguages.push_back( - LanguageModelData { metaEnum.valueToKey(i), static_cast(i) }); + LanguageModelData {getLocalLanguageName(static_cast(i)), + static_cast(i) }); } } @@ -36,11 +37,26 @@ QHash LanguageModel::roleNames() const return roles; } +QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language) +{ + QString strLanguage(""); + switch (language) { + case LanguageSettings::AvailableLanguageEnum::English: strLanguage = "English"; break; + case LanguageSettings::AvailableLanguageEnum::Russian: strLanguage = "Русский"; break; + case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break; + default: + break; + } + + return strLanguage; +} + void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum language) { switch (language) { case LanguageSettings::AvailableLanguageEnum::English: emit updateTranslations(QLocale::English); break; case LanguageSettings::AvailableLanguageEnum::Russian: emit updateTranslations(QLocale::Russian); break; + case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break; default: emit updateTranslations(QLocale::English); break; } } @@ -51,6 +67,7 @@ int LanguageModel::getCurrentLanguageIndex() switch (locale.language()) { case QLocale::English: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; + case QLocale::Chinese: return static_cast(LanguageSettings::AvailableLanguageEnum::China_cn); break; default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; } } diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index b64862dda..c8879a34b 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -11,7 +11,8 @@ namespace LanguageSettings Q_NAMESPACE enum class AvailableLanguageEnum { English, - Russian + Russian, + China_cn }; Q_ENUM_NS(AvailableLanguageEnum) @@ -59,6 +60,8 @@ protected: QHash roleNames() const override; private: + QString getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language); + QVector m_availableLanguages; std::shared_ptr m_settings; diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp index f932eb173..1f81c2c20 100644 --- a/client/ui/notificationhandler.cpp +++ b/client/ui/notificationhandler.cpp @@ -88,6 +88,10 @@ void NotificationHandler::setConnectionState(Vpn::ConnectionState state) } } +void NotificationHandler::onTranslationsUpdated() +{ +} + void NotificationHandler::unsecuredNetworkNotification(const QString& networkName) { qDebug() << "Unsecured network notification shown"; diff --git a/client/ui/notificationhandler.h b/client/ui/notificationhandler.h index 9a2182de3..abdce27b6 100644 --- a/client/ui/notificationhandler.h +++ b/client/ui/notificationhandler.h @@ -32,6 +32,7 @@ public: public slots: virtual void setConnectionState(Vpn::ConnectionState state); + virtual void onTranslationsUpdated(); signals: void notificationShown(const QString& title, const QString& message); diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index ab28d0d05..76e83da5a 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -5,6 +5,7 @@ import QtQuick.Shapes import Qt5Compat.GraphicalEffects import ConnectionState 1.0 +import PageEnum 1.0 Button { id: root @@ -137,6 +138,15 @@ Button { } onClicked: { + if (!ContainersModel.isAnyContainerInstalled()) { + PageController.setTriggeredBtConnectButton(true) + + ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex() + PageController.goToPage(PageEnum.PageSetupWizardEasy) + + return + } + if (ConnectionController.isConnectionInProgress) { ConnectionController.closeConnection() } else if (ConnectionController.isConnected) { diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 3a8a41754..2419b51a6 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -114,7 +114,7 @@ DrawerType { BasicButtonType { Layout.fillWidth: true - Layout.topMargin: 8 + Layout.topMargin: 24 defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) @@ -123,7 +123,7 @@ DrawerType { textColor: "#D7D8DB" borderWidth: 1 - text: qsTr("Show content") + text: qsTr("Show connection settings") onClicked: { configContentDrawer.visible = true diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 67ffbd9cf..f10447450 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -23,6 +23,9 @@ Item { image: backButtonImage imageColor: "#D7D8DB" + implicitWidth: 40 + implicitHeight: 40 + onClicked: { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index b0c39ddcb..a5cde9519 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -90,6 +90,9 @@ Button { anchors.centerIn: parent Image { + Layout.preferredHeight: 20 + Layout.preferredWidth: 20 + source: root.imageSource visible: root.imageSource === "" ? false : true diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index 4b94cb1ae..8429acb8b 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -123,4 +123,11 @@ RadioButton { Layout.bottomMargin: 16 } } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + enabled: false + } } diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index c22d00c24..72765d788 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -4,7 +4,6 @@ import QtQuick.Controls Drawer { id: drawer property bool needCloseButton: true - property bool isOpened: false Connections { target: PageController @@ -50,51 +49,22 @@ Drawer { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } + } + onOpened: { if (needCloseButton) { PageController.drawerOpen() } } - onAboutToHide: { + onClosed: { if (needCloseButton) { PageController.drawerClose() } - } - - onOpened: { - isOpened = true - } - - onClosed: { - isOpened = false var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) } } - - - onPositionChanged: { - if (isOpened && (position <= 0.99 && position >= 0.95)) { - mouseArea.canceled() - drawer.close() - mouseArea.exited() - dropArea.exited() - } - } - - DropArea { - id: dropArea - } - - MouseArea { - id: mouseArea - anchors.fill: parent - - onPressed: { - isOpened = true - } - } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 2feb5e17d..b91f0b7a7 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -24,6 +24,8 @@ Item { property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" property string rootButtonImageColor: "#D7D8DB" property string rootButtonBackgroundColor: "#1C1D21" + property string rootButtonBackgroundHoveredColor: "#1C1D21" + property string rootButtonBackgroundPressedColor: "#1C1D21" property string rootButtonHoveredBorderColor: "#494B50" property string rootButtonDefaultBorderColor: "#2C2D30" @@ -71,6 +73,10 @@ Item { Behavior on border.color { PropertyAnimation { duration: 200 } } + + Behavior on color { + PropertyAnimation { duration: 200 } + } } RowLayout { @@ -112,6 +118,9 @@ Item { ImageButtonType { Layout.rightMargin: 16 + implicitWidth: 40 + implicitHeight: 40 + hoverEnabled: false image: rootButtonImage imageColor: rootButtonImageColor @@ -126,12 +135,20 @@ Item { onEntered: { if (menu.visible === false) { rootButtonBackground.border.color = rootButtonHoveredBorderColor + rootButtonBackground.color = rootButtonBackgroundHoveredColor } } onExited: { if (menu.visible === false) { rootButtonBackground.border.color = rootButtonDefaultBorderColor + rootButtonBackground.color = rootButtonBackgroundColor + } + } + + onPressed: { + if (menu.visible === false) { + rootButtonBackground.color = pressed ? rootButtonBackgroundPressedColor : entered ? rootButtonHoveredBorderColor : rootButtonDefaultBorderColor } } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index 9433f52a6..4d812f6ce 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -31,6 +31,9 @@ Item { ImageButtonType { id: headerActionButton + implicitWidth: 40 + implicitHeight: 40 + image: root.actionButtonImage imageColor: "#D7D8DB" diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index b4af37844..d0ed3418c 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -36,6 +36,9 @@ Item { ImageButtonType { id: headerActionButton + implicitWidth: 40 + implicitHeight: 40 + Layout.alignment: Qt.AlignRight image: root.actionButtonImage diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index 843599a40..1ab575113 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -15,8 +15,8 @@ Button { property string imageColor: "#878B91" property string disableImageColor: "#2C2D30" - implicitWidth: 40 - implicitHeight: 40 + property alias backgroundColor: background.color + property alias backgroundRadius: background.radius hoverEnabled: true @@ -31,16 +31,16 @@ Button { id: background anchors.fill: parent - radius: 12 color: { if (root.enabled) { - if(root.pressed) { + if (root.pressed) { return pressedColor } return hovered ? hoveredColor : defaultColor } return defaultColor } + radius: 12 Behavior on color { PropertyAnimation { duration: 200 } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 7a1489c03..bb051f765 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -119,6 +119,9 @@ Item { ImageButtonType { id: rightImage + implicitWidth: 40 + implicitHeight: 40 + hoverEnabled: false image: rightImageSource imageColor: rightImageColor diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index 4b70c274d..f4f754179 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -9,71 +9,99 @@ Rectangle { property alias textArea: textArea property alias textAreaText: textArea.text + property string borderHoveredColor: "#494B50" + property string borderNormalColor: "#2C2D30" + property string borderFocusedColor: "#d7d8db" + height: 148 color: "#1C1D21" border.width: 1 - border.color: "#2C2D30" + border.color: getBorderColor(borderNormalColor) radius: 16 - FlickableType { - id: fl - interactive: false + MouseArea { + id: parentMouse + anchors.fill: parent + cursorShape: Qt.IBeamCursor + onClicked: textArea.forceActiveFocus() + hoverEnabled: true - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: textArea.implicitHeight - TextArea { - id: textArea + FlickableType { + id: fl + interactive: false - width: parent.width + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: textArea.implicitHeight + TextArea { + id: textArea - topPadding: 16 - leftPadding: 16 - anchors.topMargin: 16 - anchors.bottomMargin: 16 + width: parent.width - color: "#D7D8DB" - selectionColor: "#633303" - selectedTextColor: "#D7D8DB" - placeholderTextColor: "#878B91" + topPadding: 16 + leftPadding: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" + color: "#D7D8DB" + selectionColor: "#633303" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" - placeholderText: root.placeholderText - text: root.text + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" - onCursorVisibleChanged: { - if (textArea.cursorVisible) { - fl.interactive = true - } else { - fl.interactive = false + placeholderText: root.placeholderText + text: root.text + + onCursorVisibleChanged: { + if (textArea.cursorVisible) { + fl.interactive = true + } else { + fl.interactive = false + } + } + + wrapMode: Text.Wrap + + MouseArea { + id: textAreaMouse + anchors.fill: parent + acceptedButtons: Qt.RightButton + hoverEnabled: true + onClicked: { + fl.interactive = true + contextMenu.open() + } + } + + onFocusChanged: { + root.border.color = getBorderColor(borderNormalColor) + } + + ContextMenuType { + id: contextMenu + textObj: textArea } } + } - wrapMode: Text.Wrap + onPressed: { + root.border.color = getBorderColor(borderFocusedColor) + } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: { - fl.interactive = true - contextMenu.open() - } - } + onExited: { + root.border.color = getBorderColor(borderNormalColor) + } - ContextMenuType { - id: contextMenu - textObj: textArea - } + onEntered: { + root.border.color = getBorderColor(borderHoveredColor) } } - //todo make whole background clickable, with code below we lose ability to select text by mouse -// MouseArea { -// anchors.fill: parent -// cursorShape: Qt.IBeamCursor -// onClicked: textArea.forceActiveFocus() -// } + + function getBorderColor(noneFocusedColor) { + return textArea.focus ? root.borderFocusedColor : noneFocusedColor + } } diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index a23e93546..ac0473cf7 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -31,6 +31,7 @@ Item { property string backgroundColor: "#1c1d21" property string backgroundDisabledColor: "transparent" + property string bgBorderHoveredColor: "#494B50" implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -45,7 +46,7 @@ Item { Layout.preferredHeight: input.implicitHeight color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor radius: 16 - border.color: textField.focus ? root.borderFocusedColor : root.borderColor + border.color: getBackgroundBorderColor(root.borderColor) border.width: 1 Behavior on border.color { @@ -109,12 +110,17 @@ Item { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: contextMenu.open() + enabled: true } ContextMenuType { id: contextMenu textObj: textField } + + onFocusChanged: { + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } } } @@ -156,11 +162,28 @@ Item { MouseArea { anchors.fill: root - cursorShape: Qt.PointingHandCursor + cursorShape: Qt.IBeamCursor + + hoverEnabled: true onPressed: function(mouse) { textField.forceActiveFocus() mouse.accepted = false + + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } + + onEntered: { + backgroud.border.color = getBackgroundBorderColor(bgBorderHoveredColor) + } + + + onExited: { + backgroud.border.color = getBackgroundBorderColor(root.borderColor) } } + + function getBackgroundBorderColor(noneFocusedColor) { + return textField.focus ? root.borderFocusedColor : noneFocusedColor + } } diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml index e3b14e63a..94b480810 100644 --- a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -6,7 +6,7 @@ Text { color: "#D7D8DB" font.pixelSize: 16 - font.weight: 500 + font.weight: 600 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml index ed89b5a6b..4a7382142 100644 --- a/client/ui/qml/Controls2/TopCloseButtonType.qml +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -23,6 +23,9 @@ Popup { image: "qrc:/images/svg/close_black_24dp.svg" imageColor: "#D7D8DB" + implicitWidth: 40 + implicitHeight: 40 + onClicked: { PageController.goToDrawerRootPage() } diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index 243b12054..8dffbbce2 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -59,7 +59,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 - headerText: qsTr("Removing services from ") + name + headerText: qsTr("Removing services from %1").arg(name) } ProgressBarType { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d87965245..d395cd225 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -26,11 +26,17 @@ PageType { property string defaultServerHostName: ServersModel.defaultServerHostName property string defaultContainerName: ContainersModel.defaultContainerName + MouseArea { + anchors.fill: parent + enabled: buttonContent.state === "expanded" + onClicked: { + buttonContent.state = "collapsed" + } + } + Item { - anchors.top: parent.top - anchors.bottom: buttonBackground.top - anchors.right: parent.right - anchors.left: parent.left + anchors.fill: parent + anchors.bottomMargin: buttonContent.collapsedHeight ConnectButton { anchors.centerIn: parent @@ -41,17 +47,66 @@ PageType { target: PageController function onRestorePageHomeState(isContainerInstalled) { - menu.visible = true + buttonContent.state = "expanded" if (isContainerInstalled) { containersDropDown.menuVisible = true } } + function onForceCloseDrawer() { + buttonContent.state = "collapsed" + } + } + + MouseArea { + id: dragArea + + anchors.fill: buttonBackground + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + drag.target: buttonContent + drag.axis: Drag.YAxis + drag.maximumY: root.height - buttonContent.collapsedHeight + drag.minimumY: root.height - root.height * 0.9 + + /** If drag area is released at any point other than min or max y, transition to the other state */ + onReleased: { + if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) { + buttonContent.state = "expanded" + return + } + if (buttonContent.state === "expanded" && buttonContent.y > dragArea.drag.minimumY) { + buttonContent.state = "collapsed" + return + } + } + + onEntered: { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor + collapsedButtonHeader.opacity = 0.8 + } + onExited: { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 1 + } + onPressedChanged: { + collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 0.7 + } + + + onClicked: { + if (buttonContent.state === "collapsed") { + buttonContent.state = "expanded" + } + } } Rectangle { id: buttonBackground - anchors.fill: buttonContent + anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top } + height: root.height radius: 16 color: root.defaultColor border.color: root.borderColor @@ -69,19 +124,99 @@ PageType { ColumnLayout { id: buttonContent - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom + + /** Initial height of button content */ + property int collapsedHeight: 0 + /** True when expanded objects should be visible */ + property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active === true) + /** True when collapsed objects should be visible */ + property bool collapsedVisibility: buttonContent.state === "collapsed" && dragArea.drag.active === false + + Drag.active: dragArea.drag.active + anchors.right: root.right + anchors.left: root.left + y: root.height - buttonContent.height + + Component.onCompleted: { + buttonContent.state = "collapsed" + } + + /** Set once based on first implicit height change once all children are layed out */ + onImplicitHeightChanged: { + if (buttonContent.state === "collapsed" && collapsedHeight == 0) { + collapsedHeight = implicitHeight + } + } + + onStateChanged: { + if (buttonContent.state === "collapsed") { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + PageController.drawerClose() + return + } + if (buttonContent.state === "expanded") { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + PageController.drawerOpen() + return + } + } + + /** Two states of buttonContent, great place to add any future animations for the drawer */ + states: [ + State { + name: "collapsed" + PropertyChanges { + target: buttonContent + y: root.height - collapsedHeight + } + }, + State { + name: "expanded" + PropertyChanges { + target: buttonContent + y: dragArea.drag.minimumY + + } + } + ] + + transitions: [ + Transition { + from: "collapsed" + to: "expanded" + PropertyAnimation { + target: buttonContent + properties: "y" + duration: 200 + } + }, + Transition { + from: "expanded" + to: "collapsed" + PropertyAnimation { + target: buttonContent + properties: "y" + duration: 200 + } + } + ] RowLayout { Layout.topMargin: 24 Layout.leftMargin: 24 Layout.rightMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility spacing: 0 Header1TextType { + id: collapsedButtonHeader Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo maximumLineCount: 2 @@ -89,21 +224,40 @@ PageType { text: root.defaultServerName horizontalAlignment: Qt.AlignHCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } } - Image { - Layout.preferredWidth: 18 - Layout.preferredHeight: 18 + ImageButtonType { + id: collapsedButtonChevron - Layout.leftMargin: 12 + Layout.leftMargin: 8 - source: "qrc:/images/controls/chevron-down.svg" + hoverEnabled: false + image: "qrc:/images/controls/chevron-down.svg" + imageColor: "#d7d8db" + + icon.width: 18 + icon.height: 18 + backgroundRadius: 16 + horizontalPadding: 4 + topPadding: 4 + bottomPadding: 3 + + onClicked: { + if (buttonContent.state === "collapsed") { + buttonContent.state = "expanded" + } + } } } LabelTextType { Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility text: { var description = "" @@ -122,38 +276,13 @@ PageType { return description } } - } - - MouseArea { - anchors.fill: buttonBackground - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - - onClicked: { - menu.visible = true - } - } - - DrawerType { - id: menu - - interactive: { - if (stackView && stackView.currentItem) { - return (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) ? true : false - } else { - return false - } - } - dragMargin: buttonBackground.height + 56 // page start tabBar height - - width: parent.width - height: parent.height * 0.9 ColumnLayout { id: serversMenuHeader - anchors.top: parent.top - anchors.right: parent.right - anchors.left: parent.left + + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + visible: buttonContent.expandedVisibility Header1TextType { Layout.fillWidth: true @@ -183,8 +312,10 @@ PageType { rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" + rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) + rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65) rootButtonHoveredBorderColor: "transparent" - rootButtonPressedBorderColor: "transparent" + rootButtonDefaultBorderColor: "transparent" rootButtonTextTopMargin: 8 rootButtonTextBottomMargin: 8 @@ -232,13 +363,14 @@ PageType { Layout.topMargin: 48 Layout.leftMargin: 16 Layout.rightMargin: 16 + visible: buttonContent.expandedVisibility actionButtonImage: "qrc:/images/controls/plus.svg" headerText: qsTr("Servers") actionButtonFunction: function() { - menu.visible = false + buttonContent.state = "collapsed" connectionTypeSelection.visible = true } } @@ -248,10 +380,23 @@ PageType { } } - FlickableType { - anchors.top: serversMenuHeader.bottom - anchors.topMargin: 16 + Flickable { + id: serversContainer + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 16 contentHeight: col.implicitHeight + implicitHeight: root.height - (root.height * 0.1) - serversMenuHeader.implicitHeight - 52 //todo 52 is tabbar height + visible: buttonContent.expandedVisibility + clip: true + + ScrollBar.vertical: ScrollBar { + id: scrollBar + policy: serversContainer.height >= serversContainer.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn + } + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() Column { id: col @@ -346,13 +491,15 @@ PageType { onClicked: function() { ServersModel.currentlyProcessedIndex = index PageController.goToPage(PageEnum.PageSettingsServerInfo) - menu.visible = false + buttonContent.state = "collapsed" } } } DividerType { Layout.fillWidth: true + Layout.leftMargin: 0 + Layout.rightMargin: 0 } } } diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 8bbfab146..34ca4055b 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -173,7 +173,7 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index 016a7c881..10fe6f569 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -63,7 +63,7 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 6f5e48a20..c5536fdb9 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -43,6 +43,26 @@ PageType { headerText: qsTr("Application") } + SwitcherType { + visible: GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Allow application screenshots") + + checked: SettingsController.isScreenshotsEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isScreenshotsEnabled()) { + SettingsController.toggleScreenshotsEnabled(checked) + } + } + } + + DividerType { + visible: GC.isMobile() + } + SwitcherType { visible: !GC.isMobile() diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 78d4a6813..ae5fd7f45 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -66,7 +66,8 @@ PageType { Layout.fillWidth: true Layout.margins: 16 - text: qsTr("Use AmneziaDNS if installed on the server") + text: qsTr("Use AmneziaDNS") + descriptionText: qsTr("If AmneziaDNS is installed on the server") checked: SettingsController.isAmneziaDnsEnabled() onCheckedChanged: { diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 0bc13eaef..58ec0783d 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -53,7 +53,7 @@ PageType { id: primaryDns Layout.fillWidth: true - headerText: "Primary DNS" + headerText: qsTr("Primary DNS") textFieldText: SettingsController.primaryDns textField.validator: RegularExpressionValidator { @@ -65,7 +65,7 @@ PageType { id: secondaryDns Layout.fillWidth: true - headerText: "Secondary DNS" + headerText: qsTr("Secondary DNS") textFieldText: SettingsController.secondaryDns textField.validator: RegularExpressionValidator { diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 42f33901b..4141f51fd 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -105,7 +105,7 @@ PageType { if (GC.isMobile()) { fileName = "AmneziaVPN.log" } else { - fileName = SystemController.getFileName(qsTr("Save logs"), + fileName = SystemController.getFileName(qsTr("Save"), qsTr("Logs files (*.log)"), StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", true, diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 14d345909..998948d15 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -113,7 +113,7 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index b228a7a3e..c393d0ce5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -62,6 +62,7 @@ PageType { id: header implicitWidth: parent.width + headerTextMaximumLineCount: 10 headerText: qsTr("What is the level of internet control in your region?") } @@ -145,14 +146,13 @@ PageType { Item { implicitWidth: 1 - implicitHeight: 1 + implicitHeight: 54 } BasicButtonType { id: continueButton implicitWidth: parent.width - anchors.topMargin: 24 text: qsTr("Continue") @@ -183,6 +183,17 @@ PageType { textColor: "#D7D8DB" borderWidth: 1 + visible: { + if (PageController.isTriggeredByConnectButton()) { + PageController.setTriggeredBtConnectButton(false) + + return ContainersModel.isAnyContainerInstalled() + } + + + return true + } + text: qsTr("Set up later") onClicked: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 07eef1773..7535464ae 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -72,7 +72,7 @@ PageType { Layout.fillWidth: true - headerText: qsTr("Installing ") + name + headerText: qsTr("Installing %1").arg(name) descriptionText: description } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 16759da1b..d03b419fc 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -219,13 +219,14 @@ PageType { if (accessTypeSelector.currentIndex !== 0) { shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text } serverSelector.menuVisible = false } Component.onCompleted: { handler() + serverSelector.severSelectorIndexChanged() } function handler() { @@ -240,12 +241,14 @@ PageType { DropDownType { id: protocolSelector + visible: accessTypeSelector.currentIndex === 0 + Layout.fillWidth: true Layout.topMargin: 16 drawerHeight: 0.5 - descriptionText: qsTr("Protocols") + descriptionText: qsTr("Protocol") headerText: qsTr("Protocol") listView: ListViewWithRadioButtonType { diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/systemtray_notificationhandler.cpp index 6adc88188..2c7c695f6 100644 --- a/client/ui/systemtray_notificationhandler.cpp +++ b/client/ui/systemtray_notificationhandler.cpp @@ -17,7 +17,6 @@ #include "version.h" - SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : NotificationHandler(parent), m_systemTrayIcon(parent) @@ -26,8 +25,7 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : m_systemTrayIcon.show(); connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, &SystemTrayNotificationHandler::onTrayActivated); - - m_menu.addAction(QIcon(":/images/tray/application.png"), tr("Show") + " " + APPLICATION_NAME, this, [this](){ + m_trayActionShow = m_menu.addAction(QIcon(":/images/tray/application.png"), tr("Show") + " " + APPLICATION_NAME, this, [this](){ emit raiseRequested(); }); m_menu.addSeparator(); @@ -36,11 +34,11 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : m_menu.addSeparator(); - m_menu.addAction(QIcon(":/images/tray/link.png"), tr("Visit Website"), [&](){ + m_trayActionVisitWebSite = m_menu.addAction(QIcon(":/images/tray/link.png"), tr("Visit Website"), [&](){ QDesktopServices::openUrl(QUrl("https://amnezia.org")); }); - m_menu.addAction(QIcon(":/images/tray/cancel.png"), tr("Quit") + " " + APPLICATION_NAME, this, [&](){ + m_trayActionQuit = m_menu.addAction(QIcon(":/images/tray/cancel.png"), tr("Quit") + " " + APPLICATION_NAME, this, [&](){ qApp->quit(); }); @@ -57,6 +55,15 @@ void SystemTrayNotificationHandler::setConnectionState(Vpn::ConnectionState stat NotificationHandler::setConnectionState(state); } +void SystemTrayNotificationHandler::onTranslationsUpdated() +{ + m_trayActionShow->setText(tr("Show") + " " + APPLICATION_NAME); + m_trayActionConnect->setText(tr("Connect")); + m_trayActionDisconnect->setText(tr("Disconnect")); + m_trayActionVisitWebSite->setText(tr("Visit Website")); + m_trayActionQuit->setText(tr("Quit")+ " " + APPLICATION_NAME); +} + void SystemTrayNotificationHandler::setTrayIcon(const QString &iconPath) { QIcon trayIconMask(QPixmap(iconPath).scaled(128,128)); diff --git a/client/ui/systemtray_notificationhandler.h b/client/ui/systemtray_notificationhandler.h index 96134f140..60bf0b357 100644 --- a/client/ui/systemtray_notificationhandler.h +++ b/client/ui/systemtray_notificationhandler.h @@ -19,6 +19,8 @@ public: void setConnectionState(Vpn::ConnectionState state) override; + void onTranslationsUpdated() override; + protected: virtual void notify(Message type, const QString& title, const QString& message, int timerMsec) override; @@ -35,9 +37,11 @@ private: QMenu m_menu; QSystemTrayIcon m_systemTrayIcon; + QAction* m_trayActionShow = nullptr; QAction* m_trayActionConnect = nullptr; QAction* m_trayActionDisconnect = nullptr; - QAction* m_preferencesAction = nullptr; + QAction* m_trayActionVisitWebSite = nullptr; + QAction* m_trayActionQuit = nullptr; QAction* m_statusLabel = nullptr; QAction* m_separator = nullptr;