updating: show the reason why the update couldn't be completed

- include nonstd::expected library for modern error-handling
- add localized strings
- refactor updating.cpp
This commit is contained in:
yuyoyuppe 2020-11-18 17:31:15 +03:00 committed by Andrey Nekrasov
parent 63ce7ca981
commit 289532d7c8
14 changed files with 158 additions and 85 deletions

View file

@ -1327,6 +1327,7 @@ MAPTOSAMESHORTCUT
MAPVK
Markdig
martinchrzan
martinmoene
MATCHALLOCCURENCES
MATCHMODE
MAXIMIZEBOX
@ -1504,6 +1505,7 @@ nonclient
NONCONVERT
NONELEVATED
NONINFRINGEMENT
nonstd
nonwin
NOOWNERZORDER
NOPARENTNOTIFY
@ -1573,6 +1575,7 @@ Onboarding
onebranch
onedrive
onedrivelogo
onstd
ONITEM
OOBE
opencode
@ -2148,6 +2151,7 @@ subfolder
Subheader
subkey
SUBLANG
sublicensable
subquery
subsetted
subsetter

4
.gitmodules vendored
View file

@ -5,3 +5,7 @@
[submodule "deps/cxxopts"]
path = deps/cxxopts
url = https://github.com/jarro2783/cxxopts.git
[submodule "deps/expected-lite"]
path = deps/expected-lite
url = https://github.com/martinmoene/expected-lite.git

View file

@ -153,7 +153,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## PowerToy: Installer
## PowerToy: Installer/Runner
### spdlog
**Source**: https://github.com/gabime/spdlog
@ -207,3 +207,33 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
### expected-lite
**Source**: https://github.com/martinmoene/expected-lite
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

1
deps/expected-lite vendored Submodule

@ -0,0 +1 @@
Subproject commit 076e96f6832923ea56b67e37d77806c5d01a343f

8
deps/expected.props vendored Normal file
View file

@ -0,0 +1,8 @@
<Project>
<Import Project="restore_git_submodules.props" Condition="'$(RestoreGitSubmodulesImported)' == ''" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)expected-lite\include\nonstd\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
</Project>

View file

@ -88,7 +88,7 @@
<data name="GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT" xml:space="preserve">
<value>An update to PowerToys is available. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UNAVAILABLE" xml:space="preserve">
<data name="GITHUB_NEW_VERSION_UP_TO_DATE" xml:space="preserve">
<value>PowerToys is up to date.</value>
</data>
<data name="GITHUB_NEW_VERSION_VISIT" xml:space="preserve">
@ -153,5 +153,12 @@
</data>
<data name="SNOOZE_BUTTON" xml:space="preserve">
<value>Snooze</value>
</data>
<data name="GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR" xml:space="preserve">
<value>Updating from a local build is not supported.</value>
<comment>User cannot autoupdate from a locally-built PowerToys version</comment>
</data>
<data name="GITHUB_NEW_VERSION_CHECK_ERROR" xml:space="preserve">
<value>Failed to connect to the server. Check your network connection or retry later.</value>
</data>
</root>
</root>

View file

@ -88,7 +88,7 @@
<data name="GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT" xml:space="preserve">
<value>An update to PowerToys is available. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UNAVAILABLE" xml:space="preserve">
<data name="GITHUB_NEW_VERSION_UP_TO_DATE" xml:space="preserve">
<value>PowerToys is up to date.</value>
</data>
<data name="GITHUB_NEW_VERSION_VISIT" xml:space="preserve">
@ -126,5 +126,12 @@
</data>
<data name="SNOOZE_BUTTON" xml:space="preserve">
<value>Snooze</value>
</data>
<data name="GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR" xml:space="preserve">
<value>Updating from a local build is not supported.</value>
<comment>User cannot autoupdate from a locally-built PowerToys version</comment>
</data>
<data name="GITHUB_NEW_VERSION_CHECK_ERROR" xml:space="preserve">
<value>Failed to connect to the server. Check your network connection or retry later.</value>
</data>
</root>
</root>

View file

@ -22,13 +22,12 @@ namespace updating
return current_version_to_next_version;
}
void show_unavailable(const notifications::strings& strings)
void show_unavailable(const notifications::strings& strings, std::wstring reason)
{
remove_toasts(UPDATING_PROCESS_TOAST_TAG);
toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false };
std::wstring contents = strings.GITHUB_NEW_VERSION_UNAVAILABLE;
show_toast(std::move(contents), strings.TOAST_TITLE, std::move(toast_params));
show_toast(std::move(reason), strings.TOAST_TITLE, std::move(toast_params));
}
void show_available(const updating::new_version_download_info& info, const notifications::strings& strings)
@ -120,8 +119,7 @@ namespace updating
strings.GITHUB_NEW_VERSION_SNOOZE_TITLE,
{ { strings.GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 },
{ strings.GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } },
strings.SNOOZE_BUTTON
} },
strings.SNOOZE_BUTTON } },
std::move(toast_params));
}

View file

@ -10,30 +10,36 @@ namespace updating
{
struct strings
{
std::wstring DOWNLOAD_COMPLETE;
std::wstring DOWNLOAD_IN_PROGRESS;
std::wstring GITHUB_NEW_VERSION_ABORT;
std::wstring GITHUB_NEW_VERSION_AVAILABLE;
std::wstring GITHUB_NEW_VERSION_DOWNLOAD_STARTED;
std::wstring GITHUB_NEW_VERSION_READY_TO_INSTALL;
std::wstring GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
std::wstring GITHUB_NEW_VERSION_CHECK_ERROR;
std::wstring GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR;
std::wstring GITHUB_NEW_VERSION_DOWNLOAD_STARTED;
std::wstring GITHUB_NEW_VERSION_MORE_INFO;
std::wstring GITHUB_NEW_VERSION_READY_TO_INSTALL;
std::wstring GITHUB_NEW_VERSION_SNOOZE_TITLE;
std::wstring GITHUB_NEW_VERSION_UP_TO_DATE;
std::wstring GITHUB_NEW_VERSION_UPDATE_NOW;
std::wstring GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART;
std::wstring UNINSTALLATION_UNKNOWN_ERROR;
std::wstring GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
std::wstring GITHUB_NEW_VERSION_UNAVAILABLE;
std::wstring GITHUB_NEW_VERSION_VISIT;
std::wstring GITHUB_NEW_VERSION_MORE_INFO;
std::wstring GITHUB_NEW_VERSION_ABORT;
std::wstring GITHUB_NEW_VERSION_SNOOZE_TITLE;
std::wstring SNOOZE_BUTTON;
std::wstring GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D;
std::wstring GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D;
std::wstring DOWNLOAD_IN_PROGRESS;
std::wstring DOWNLOAD_COMPLETE;
std::wstring TOAST_TITLE;
std::wstring GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR;
std::wstring GITHUB_NEW_VERSION_VISIT;
std::wstring OFFER_UNINSTALL_MSI;
std::wstring OFFER_UNINSTALL_MSI_TITLE;
std::wstring SNOOZE_BUTTON;
std::wstring TOAST_TITLE;
std::wstring UNINSTALLATION_UNKNOWN_ERROR;
};
void show_unavailable(const notifications::strings& strings);
void show_unavailable(const notifications::strings& strings, std::wstring reason);
void show_available(const updating::new_version_download_info& info, const strings&);
void show_download_start(const updating::new_version_download_info& info, const strings&);
void show_visit_github(const updating::new_version_download_info& info, const strings&);
@ -45,28 +51,30 @@ namespace updating
}
}
#define create_notifications_strings() \
::updating::notifications::strings \
{ \
.GITHUB_NEW_VERSION_AVAILABLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE), \
.GITHUB_NEW_VERSION_DOWNLOAD_STARTED = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_DOWNLOAD_STARTED), \
.GITHUB_NEW_VERSION_READY_TO_INSTALL = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_READY_TO_INSTALL), \
.GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR), \
.GITHUB_NEW_VERSION_UPDATE_NOW = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_NOW), \
.GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART), \
.UNINSTALLATION_UNKNOWN_ERROR = GET_RESOURCE_STRING(IDS_UNINSTALLATION_UNKNOWN_ERROR), \
.GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT), \
.GITHUB_NEW_VERSION_UNAVAILABLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UNAVAILABLE), \
.GITHUB_NEW_VERSION_VISIT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_VISIT), \
.GITHUB_NEW_VERSION_MORE_INFO = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_MORE_INFO), \
.GITHUB_NEW_VERSION_ABORT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_ABORT), \
.GITHUB_NEW_VERSION_SNOOZE_TITLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_SNOOZE_TITLE), \
.SNOOZE_BUTTON = GET_RESOURCE_STRING(IDS_SNOOZE_BUTTON), \
.GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D), \
.GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D), \
.DOWNLOAD_IN_PROGRESS = GET_RESOURCE_STRING(IDS_DOWNLOAD_IN_PROGRESS), \
.DOWNLOAD_COMPLETE = GET_RESOURCE_STRING(IDS_DOWNLOAD_COMPLETE), \
.TOAST_TITLE = GET_RESOURCE_STRING(IDS_TOAST_TITLE), \
.OFFER_UNINSTALL_MSI = GET_RESOURCE_STRING(IDS_OFFER_UNINSTALL_MSI), \
.OFFER_UNINSTALL_MSI_TITLE = GET_RESOURCE_STRING(IDS_OFFER_UNINSTALL_MSI_TITLE) \
#define create_notifications_strings() \
::updating::notifications::strings \
{ \
.DOWNLOAD_COMPLETE = GET_RESOURCE_STRING(IDS_DOWNLOAD_COMPLETE), \
.DOWNLOAD_IN_PROGRESS = GET_RESOURCE_STRING(IDS_DOWNLOAD_IN_PROGRESS), \
.GITHUB_NEW_VERSION_ABORT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_ABORT), \
.GITHUB_NEW_VERSION_AVAILABLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE), \
.GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT), \
.GITHUB_NEW_VERSION_CHECK_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_CHECK_ERROR), \
.GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR), \
.GITHUB_NEW_VERSION_DOWNLOAD_STARTED = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_DOWNLOAD_STARTED), \
.GITHUB_NEW_VERSION_MORE_INFO = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_MORE_INFO), \
.GITHUB_NEW_VERSION_READY_TO_INSTALL = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_READY_TO_INSTALL), \
.GITHUB_NEW_VERSION_SNOOZE_TITLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_SNOOZE_TITLE), \
.GITHUB_NEW_VERSION_UP_TO_DATE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UP_TO_DATE), \
.GITHUB_NEW_VERSION_UPDATE_NOW = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_NOW), \
.GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART), \
.GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D), \
.GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D), \
.GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR), \
.GITHUB_NEW_VERSION_VISIT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_VISIT), \
.OFFER_UNINSTALL_MSI = GET_RESOURCE_STRING(IDS_OFFER_UNINSTALL_MSI), \
.OFFER_UNINSTALL_MSI_TITLE = GET_RESOURCE_STRING(IDS_OFFER_UNINSTALL_MSI_TITLE), \
.SNOOZE_BUTTON = GET_RESOURCE_STRING(IDS_SNOOZE_BUTTON), \
.TOAST_TITLE = GET_RESOURCE_STRING(IDS_TOAST_TITLE), \
.UNINSTALLATION_UNKNOWN_ERROR = GET_RESOURCE_STRING(IDS_UNINSTALLATION_UNKNOWN_ERROR) \
}

View file

@ -15,5 +15,6 @@
#include <ShlObj_core.h>
#include <shellapi.h>
#include <filesystem>
#include <expected.hpp>
#endif //PCH_H

View file

@ -28,12 +28,12 @@ namespace // Strings in this namespace should not be localized
namespace updating
{
std::future<std::optional<new_version_download_info>> get_new_github_version_info_async()
std::future<nonstd::expected<new_version_download_info, std::wstring>> get_new_github_version_info_async(const notifications::strings& strings)
{
// If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
if (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
{
co_return std::nullopt;
co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR);
}
try
{
@ -46,39 +46,37 @@ namespace updating
VersionHelper github_version(winrt::to_string(new_version));
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
if (github_version > current_version)
if (github_version <= current_version)
{
const std::wstring_view required_architecture = get_architecture_string(get_current_architecture());
constexpr const std::wstring_view required_filename_pattern = updating::INSTALLER_FILENAME_PATTERN;
// Desc-sorted by its priority
const std::array<std::wstring_view, 2> asset_extensions = { L".exe", L".msi" };
for (const auto asset_extension : asset_extensions)
{
for (auto asset_elem : json_body.GetNamedArray(L"assets"))
{
auto asset{ asset_elem.GetObjectW() };
std::wstring filename_lower = asset.GetNamedString(L"name", {}).c_str();
std::transform(begin(filename_lower), end(filename_lower), begin(filename_lower), ::towlower);
co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_UP_TO_DATE);
}
const bool extension_matched = filename_lower.ends_with(asset_extension);
const bool architecture_matched = filename_lower.find(required_architecture) != std::wstring::npos;
const bool filename_matched = filename_lower.find(required_filename_pattern) != std::wstring::npos;
if (extension_matched && architecture_matched && filename_matched)
{
winrt::Windows::Foundation::Uri msi_download_url{ asset.GetNamedString(L"browser_download_url") };
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str(), std::move(msi_download_url), std::move(filename_lower) };
}
const std::wstring_view required_architecture = get_architecture_string(get_current_architecture());
constexpr const std::wstring_view required_filename_pattern = updating::INSTALLER_FILENAME_PATTERN;
// Desc-sorted by its priority
const std::array<std::wstring_view, 2> asset_extensions = { L".exe", L".msi" };
for (const auto asset_extension : asset_extensions)
{
for (auto asset_elem : json_body.GetNamedArray(L"assets"))
{
auto asset{ asset_elem.GetObjectW() };
std::wstring filename_lower = asset.GetNamedString(L"name", {}).c_str();
std::transform(begin(filename_lower), end(filename_lower), begin(filename_lower), ::towlower);
const bool extension_matched = filename_lower.ends_with(asset_extension);
const bool architecture_matched = filename_lower.find(required_architecture) != std::wstring::npos;
const bool filename_matched = filename_lower.find(required_filename_pattern) != std::wstring::npos;
if (extension_matched && architecture_matched && filename_matched)
{
winrt::Windows::Foundation::Uri msi_download_url{ asset.GetNamedString(L"browser_download_url") };
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str(), std::move(msi_download_url), std::move(filename_lower) };
}
}
}
else
{
co_return std::nullopt;
}
}
catch (...)
{
co_return std::nullopt;
co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_CHECK_ERROR);
}
}
@ -106,7 +104,7 @@ namespace updating
std::future<void> try_autoupdate(const bool download_updates_automatically, const notifications::strings& strings)
{
const auto new_version = co_await get_new_github_version_info_async();
const auto new_version = co_await get_new_github_version_info_async(strings);
if (!new_version)
{
co_return;
@ -146,10 +144,10 @@ namespace updating
std::future<std::wstring> check_new_version_available(const notifications::strings& strings)
{
const auto new_version = co_await get_new_github_version_info_async();
auto new_version = co_await get_new_github_version_info_async(strings);
if (!new_version)
{
updating::notifications::show_unavailable(strings);
updating::notifications::show_unavailable(strings, std::move(new_version.error()));
co_return VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring();
}
@ -159,7 +157,7 @@ namespace updating
std::future<std::wstring> download_update(const notifications::strings& strings)
{
const auto new_version = co_await get_new_github_version_info_async();
const auto new_version = co_await get_new_github_version_info_async(strings);
if (!new_version)
{
co_return L"";
@ -185,4 +183,4 @@ namespace updating
co_return new_version->installer_filename;
}
}
}

View file

@ -13,13 +13,12 @@ namespace updating
{
struct new_version_download_info
{
winrt::Windows::Foundation::Uri release_page_uri;
winrt::Windows::Foundation::Uri release_page_uri = nullptr;
std::wstring version_string;
winrt::Windows::Foundation::Uri installer_download_url;
winrt::Windows::Foundation::Uri installer_download_url = nullptr;
std::wstring installer_filename;
};
std::future<std::optional<new_version_download_info>> get_new_github_version_info_async();
std::future<void> try_autoupdate(const bool download_updates_automatically, const notifications::strings&);
std::filesystem::path get_pending_updates_path();

View file

@ -28,6 +28,7 @@
<ProjectName>updating</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\deps\expected.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>

View file

@ -115,7 +115,7 @@
<data name="GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT" xml:space="preserve">
<value>An update to PowerToys is available. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UNAVAILABLE" xml:space="preserve">
<data name="GITHUB_NEW_VERSION_UP_TO_DATE" xml:space="preserve">
<value>PowerToys is up to date.</value>
</data>
<data name="GITHUB_NEW_VERSION_VISIT" xml:space="preserve">
@ -160,5 +160,12 @@
<data name="EXIT_MENU_TEXT" xml:space="preserve">
<value>Exit</value>
<comment>Exit as a verb, as in Exit the application</comment>
</data>
</data>
<data name="GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR" xml:space="preserve">
<value>Updating from a local build is not supported.</value>
<comment>User cannot autoupdate from a locally-built PowerToys version</comment>
</data>
<data name="GITHUB_NEW_VERSION_CHECK_ERROR" xml:space="preserve">
<value>Failed to connect to the server. Check your network connection or retry later.</value>
</data>
</root>