commit ec2b2e50b05555811684a720e3bef8a8210eab66 parent 4f73d7cfff75d478176cf31d4abad0b8b5ff8dd4 Author: JohnMcPMS <johnmcp@microsoft.com> Date: Wed, 20 May 2020 15:17:04 -0700 Work towards enforcing output localization (#177) This change introduces several new types and eases use of localized string resources. The eventual goal is to have compile time enforcement of output localization. That isn't quite achieved here, but the groundwork is laid. What remains is largely moving strings into resources and tagging things appropriately. There will be some thought/effort required in the TableOutput helper, but beyond that I don't see much trouble. The new types for localized strings allow for direct output of a resource (Resource::StringId), making the code flow more naturally. Additionally, if a string needs to be loaded manually to hold onto it, the Resource::LocString type takes a StringId as well. The Utility::LocInd(View/String) types allow a value to be tagged as localization independent. This is true for many strings that come from external sources, or are simply decorations in the output. It can be applied to a string that is already localized, or that has no localization. The Execution::Reporter::OutputStream has a commented line that will make compilation fail for any type not defined as allowed above. This can be used to find additional code in need of update, and will eventually be enabled to enforce things long term. Finally, several strings have been moved to resources, and many places have been changed for this format. Many more remain for a future PR. For now manual review should hopefully keep things from backsliding too much. Diffstat:
40 files changed, 694 insertions(+), 325 deletions(-)
diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -17,67 +17,67 @@ namespace AppInstaller::CLI switch (type) { case Args::Type::Query: - return Argument{ "query", 'q', Args::Type::Query, Resources::GetInstance().ResolveWingetString(L"QueryArgumentDescription").c_str(), ArgumentType::Positional }; + return Argument{ "query", 'q', Args::Type::Query, Resource::String::QueryArgumentDescription, ArgumentType::Positional }; case Args::Type::Manifest: - return Argument{ "manifest", 'm', Args::Type::Manifest, Resources::GetInstance().ResolveWingetString(L"ManifestArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Help }; + return Argument{ "manifest", 'm', Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Visibility::Help }; case Args::Type::Id: - return Argument{ "id", None, Args::Type::Id,Resources::GetInstance().ResolveWingetString(L"IdArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Help }; + return Argument{ "id", None, Args::Type::Id,Resource::String::IdArgumentDescription, ArgumentType::Standard, Visibility::Help }; case Args::Type::Name: - return Argument{ "name", None, Args::Type::Name, Resources::GetInstance().ResolveWingetString(L"NameArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Help }; + return Argument{ "name", None, Args::Type::Name, Resource::String::NameArgumentDescription, ArgumentType::Standard, Visibility::Help }; case Args::Type::Moniker: - return Argument{ "moniker", None, Args::Type::Moniker, Resources::GetInstance().ResolveWingetString(L"MonikerArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Help }; + return Argument{ "moniker", None, Args::Type::Moniker, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Visibility::Help }; case Args::Type::Tag: - return Argument{ "tag", None, Args::Type::Tag, Resources::GetInstance().ResolveWingetString(L"TagArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Help }; + return Argument{ "tag", None, Args::Type::Tag, Resource::String::TagArgumentDescription, ArgumentType::Standard, Visibility::Help }; case Args::Type::Command: - return Argument{ "command", None, Args::Type::Command, Resources::GetInstance().ResolveWingetString(L"CommandArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Help }; + return Argument{ "command", None, Args::Type::Command, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Visibility::Help }; case Args::Type::Source: - return Argument{ "source", 's', Args::Type::Source, Resources::GetInstance().ResolveWingetString(L"SourceArgumentDescription").c_str(), ArgumentType::Standard }; + return Argument{ "source", 's', Args::Type::Source, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; case Args::Type::Count: - return Argument{ "count", 'n', Args::Type::Count, Resources::GetInstance().ResolveWingetString(L"CountArgumentDescription").c_str(), ArgumentType::Standard }; + return Argument{ "count", 'n', Args::Type::Count, Resource::String::CountArgumentDescription, ArgumentType::Standard }; case Args::Type::Exact: - return Argument{ "exact", 'e', Args::Type::Exact, Resources::GetInstance().ResolveWingetString(L"ExactArgumentDescription").c_str(), ArgumentType::Flag }; + return Argument{ "exact", 'e', Args::Type::Exact, Resource::String::ExactArgumentDescription, ArgumentType::Flag }; case Args::Type::Version: - return Argument{ "version", 'v', Args::Type::Version, Resources::GetInstance().ResolveWingetString(L"VersionArgumentDescription").c_str(), ArgumentType::Standard }; + return Argument{ "version", 'v', Args::Type::Version, Resource::String::VersionArgumentDescription, ArgumentType::Standard }; case Args::Type::Channel: - return Argument{ "channel", 'c', Args::Type::Channel, Resources::GetInstance().ResolveWingetString(L"ChannelArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Hidden }; + return Argument{ "channel", 'c', Args::Type::Channel, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Visibility::Hidden }; case Args::Type::Interactive: - return Argument{ "interactive", 'i', Args::Type::Interactive, Resources::GetInstance().ResolveWingetString(L"InteractiveArgumentDescription").c_str(), ArgumentType::Flag }; + return Argument{ "interactive", 'i', Args::Type::Interactive, Resource::String::InteractiveArgumentDescription, ArgumentType::Flag }; case Args::Type::Silent: - return Argument{ "silent", 'h', Args::Type::Silent, Resources::GetInstance().ResolveWingetString(L"SilentArgumentDescription").c_str(), ArgumentType::Flag }; + return Argument{ "silent", 'h', Args::Type::Silent, Resource::String::SilentArgumentDescription, ArgumentType::Flag }; case Args::Type::Language: - return Argument{ "lang", 'a', Args::Type::Language, Resources::GetInstance().ResolveWingetString(L"LanguageArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Hidden }; + return Argument{ "lang", 'a', Args::Type::Language, Resource::String::LanguageArgumentDescription, ArgumentType::Standard, Visibility::Hidden }; case Args::Type::Log: - return Argument{ "log", 'o', Args::Type::Log, Resources::GetInstance().ResolveWingetString(L"LogArgumentDesciption").c_str(), ArgumentType::Standard }; + return Argument{ "log", 'o', Args::Type::Log, Resource::String::LogArgumentDesciption, ArgumentType::Standard }; case Args::Type::Override: - return Argument{ "override", None, Args::Type::Override, Resources::GetInstance().ResolveWingetString(L"OverrideArgumentDescription").c_str(), ArgumentType::Standard, Visibility::Help }; + return Argument{ "override", None, Args::Type::Override, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Visibility::Help }; case Args::Type::InstallLocation: - return Argument{ "location", 'l', Args::Type::InstallLocation, Resources::GetInstance().ResolveWingetString(L"LocationArgumentDescription").c_str(), ArgumentType::Standard }; + return Argument{ "location", 'l', Args::Type::InstallLocation, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; case Args::Type::HashFile: - return Argument{ "file", 'f', Args::Type::HashFile, Resources::GetInstance().ResolveWingetString(L"FileArgumentDescription").c_str(), ArgumentType::Positional, true }; + return Argument{ "file", 'f', Args::Type::HashFile, Resource::String::FileArgumentDescription, ArgumentType::Positional, true }; case Args::Type::Msix: - return Argument{ "msix", 'm', Args::Type::Msix, Resources::GetInstance().ResolveWingetString(L"MsixArgumentDescription").c_str(), ArgumentType::Flag }; + return Argument{ "msix", 'm', Args::Type::Msix, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; case Args::Type::ListVersions: - return Argument{ "versions", None, Args::Type::ListVersions, Resources::GetInstance().ResolveWingetString(L"VersionsArgumentDescription").c_str(), ArgumentType::Flag }; + return Argument{ "versions", None, Args::Type::ListVersions, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; case Args::Type::Help: - return Argument{ "help", APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR, Args::Type::Help, Resources::GetInstance().ResolveWingetString(L"HelpArgumentDescription").c_str(), ArgumentType::Flag }; + return Argument{ "help", APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR, Args::Type::Help, Resource::String::HelpArgumentDescription, ArgumentType::Flag }; case Args::Type::SourceName: - return Argument{ "name", 'n', Args::Type::SourceName,Resources::GetInstance().ResolveWingetString(L"SourceNameArgumentDescription").c_str(), ArgumentType::Positional, false }; + return Argument{ "name", 'n', Args::Type::SourceName,Resource::String::SourceNameArgumentDescription, ArgumentType::Positional, false }; case Args::Type::SourceArg: - return Argument{ "arg", 'a', Args::Type::SourceArg, Resources::GetInstance().ResolveWingetString(L"SourceArgArgumentDescription").c_str(), ArgumentType::Positional, true }; + return Argument{ "arg", 'a', Args::Type::SourceArg, Resource::String::SourceArgArgumentDescription, ArgumentType::Positional, true }; case Args::Type::SourceType: - return Argument{ "type", 't', Args::Type::SourceType, Resources::GetInstance().ResolveWingetString(L"SourceTypeArgumentDescription").c_str(), ArgumentType::Positional }; + return Argument{ "type", 't', Args::Type::SourceType, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional }; case Args::Type::ValidateManifest: - return Argument{ "manifest", None, Args::Type::ValidateManifest, Resources::GetInstance().ResolveWingetString(L"ValidateManifestArgumentDescription").c_str(), ArgumentType::Positional, true }; + return Argument{ "manifest", None, Args::Type::ValidateManifest, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; case Args::Type::NoVT: - return Argument{ "no-vt", None, Args::Type::NoVT, Resources::GetInstance().ResolveWingetString(L"NoVTArguementDescription").c_str(), ArgumentType::Flag, Visibility::Hidden }; + return Argument{ "no-vt", None, Args::Type::NoVT, Resource::String::NoVTArguementDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::RainbowStyle: - return Argument{ "rainbow", None, Args::Type::RainbowStyle, Resources::GetInstance().ResolveWingetString(L"RainbowArgumentDescription").c_str(), ArgumentType::Flag, Visibility::Hidden }; + return Argument{ "rainbow", None, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::PlainStyle: - return Argument{ "plain", None, Args::Type::PlainStyle, Resources::GetInstance().ResolveWingetString(L"PlainArgumentDescription").c_str(), ArgumentType::Flag, Visibility::Hidden }; + return Argument{ "plain", None, Args::Type::PlainStyle, Resource::String::PlainArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::Force: - return Argument{ "force", None, Args::Type::Force, Resources::GetInstance().ResolveWingetString(L"ForceArgumentDescription").c_str(), ArgumentType::Flag }; + return Argument{ "force", None, Args::Type::Force, Resource::String::ForceArgumentDescription, ArgumentType::Flag }; case Args::Type::VerboseLogs: - return Argument{ "verbose-logs", None, Args::Type::VerboseLogs, Resources::GetInstance().ResolveWingetString(L"VerboseLogsArgumentDescription").c_str(), ArgumentType::Flag }; + return Argument{ "verbose-logs", None, Args::Type::VerboseLogs, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include "ExecutionContext.h" +#include "Resources.h" #include <string> #include <string_view> @@ -43,22 +44,22 @@ namespace AppInstaller::CLI // An argument to a command. struct Argument { - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, std::string desc) : + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc) : m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, std::string desc, bool required) : + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, bool required) : m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_required(required) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, std::string desc, ArgumentType type) : + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, std::string desc, ArgumentType type, Visibility visibility) : + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Visibility visibility) : m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, std::string desc, ArgumentType type, bool required) : + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required) : m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_required(required) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, std::string desc, ArgumentType type, Visibility visibility, bool required) : + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Visibility visibility, bool required) : m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required) {} ~Argument() = default; @@ -75,10 +76,11 @@ namespace AppInstaller::CLI // Gets the common arguments for all commands. static void GetCommon(std::vector<Argument>& args); - std::string_view Name() const { return m_name; } + // Arguments are not localized at this time. + Utility::LocIndView Name() const { return Utility::LocIndView{ m_name }; } char Alias() const { return m_alias; } Execution::Args::Type ExecArgType() const { return m_execArgType; } - const std::string& Description() const { return m_desc; } + const Resource::StringId& Description() const { return m_desc; } bool Required() const { return m_required; } ArgumentType Type() const { return m_type; } size_t Limit() const { return m_countLimit; } @@ -90,7 +92,7 @@ namespace AppInstaller::CLI std::string_view m_name; char m_alias; Execution::Args::Type m_execArgType; - std::string m_desc; + Resource::StringId m_desc; bool m_required = false; ArgumentType m_type = ArgumentType::Standard; ::AppInstaller::CLI::Visibility m_visibility = Visibility::Example; diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -7,6 +7,7 @@ namespace AppInstaller::CLI { using namespace std::string_view_literals; + using namespace Utility::literals; Command::Command(std::string_view name, std::string_view parent) : m_name(name) @@ -27,8 +28,8 @@ namespace AppInstaller::CLI void Command::OutputIntroHeader(Execution::Reporter& reporter) const { reporter.Info() << - "Windows Package Manager v" << Runtime::GetClientVersion() << " " << Resources::GetInstance().ResolveWingetString(L"PreviewVersion") << std::endl << - "Copyright (c) Microsoft Corporation. All rights reserved." << std::endl; + "Windows Package Manager v"_liv << Runtime::GetClientVersion() << ' ' << Resource::String::PreviewVersion << std::endl << + Resource::String::MainCopyrightNotice << std::endl; } void Command::OutputHelp(Execution::Reporter& reporter, const CommandException* exception) const @@ -41,17 +42,18 @@ namespace AppInstaller::CLI if (exception) { reporter.Error() << - exception->Message() << " : '" << exception->Param() << '\'' << std::endl << + exception->Message() << " : '"_liv << exception->Param() << '\'' << std::endl << std::endl; } // Description auto infoOut = reporter.Info(); infoOut << - GetLongDescription() << std::endl << + LongDescription() << std::endl << std::endl; // Example usage for this command + // First create the command chain for output std::string commandChain = FullName(); size_t firstSplit = commandChain.find_first_of(ParentSplitChar); if (firstSplit == std::string::npos) @@ -71,7 +73,7 @@ namespace AppInstaller::CLI } // Output the command preamble and command chain - infoOut << "usage: winget" << commandChain; + infoOut << Resource::String::Usage << ": winget"_liv << Utility::LocIndView{ commandChain }; auto commands = GetCommands(); auto arguments = GetArguments(); @@ -89,7 +91,7 @@ namespace AppInstaller::CLI infoOut << '['; } - infoOut << "<command>"; + infoOut << '<' << Resource::String::Command << '>'; if (!arguments.empty()) { @@ -122,7 +124,7 @@ namespace AppInstaller::CLI infoOut << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias(); } - infoOut << "] <" << arg.Name() << '>'; + infoOut << "] <"_liv << arg.Name() << '>'; if (!arg.Required()) { @@ -132,7 +134,7 @@ namespace AppInstaller::CLI else { hasOptions = true; - infoOut << " [<options>]"; + infoOut << " [<"_liv << Resource::String::Options << ">]"_liv; break; } } @@ -145,11 +147,11 @@ namespace AppInstaller::CLI { if (Name() == FullName()) { - infoOut << Resources::GetInstance().ResolveWingetString(L"AvailableCommands") << std::endl; + infoOut << Resource::String::AvailableCommands << std::endl; } else { - infoOut << Resources::GetInstance().ResolveWingetString(L"AvailableSubcommands") << std::endl; + infoOut << Resource::String::AvailableSubcommands << std::endl; } size_t maxCommandNameLength = 0; @@ -161,13 +163,13 @@ namespace AppInstaller::CLI for (const auto& command : commands) { size_t fillChars = (maxCommandNameLength - command->Name().length()) + 2; - infoOut << " " << Execution::HelpCommandEmphasis << command->Name() << std::string(fillChars, ' ') << command->ShortDescription() << std::endl; + infoOut << " "_liv << Execution::HelpCommandEmphasis << command->Name() << Utility::LocIndString{ std::string(fillChars, ' ') } << command->ShortDescription() << std::endl; } infoOut << std::endl << - Resources::GetInstance().ResolveWingetString(L"HelpForDetails") - << " [" << APPINSTALLER_CLI_HELP_ARGUMENT << ']' << std::endl; + Resource::String::HelpForDetails + << " ["_liv << APPINSTALLER_CLI_HELP_ARGUMENT << ']' << std::endl; } if (!arguments.empty()) @@ -197,7 +199,7 @@ namespace AppInstaller::CLI if (hasArguments) { - infoOut << Resources::GetInstance().ResolveWingetString(L"AvailableArguements") << std::endl; + infoOut << Resource::String::AvailableArguements << std::endl; size_t i = 0; for (const auto& arg : GetArguments()) @@ -208,7 +210,7 @@ namespace AppInstaller::CLI if (arg.Type() == ArgumentType::Positional) { size_t fillChars = (maxArgNameLength - argName.length()) + 2; - infoOut << " " << Execution::HelpArgumentEmphasis << argName << std::string(fillChars, ' ') << arg.Description() << std::endl; + infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; } } } @@ -221,7 +223,7 @@ namespace AppInstaller::CLI infoOut << std::endl; } - infoOut << Resources::GetInstance().ResolveWingetString(L"AvailableOptions") << std::endl; + infoOut << Resource::String::AvailableOptions << std::endl; size_t i = 0; for (const auto& arg : GetArguments()) @@ -232,7 +234,7 @@ namespace AppInstaller::CLI if (arg.Type() != ArgumentType::Positional) { size_t fillChars = (maxArgNameLength - argName.length()) + 2; - infoOut << " " << Execution::HelpArgumentEmphasis << argName << std::string(fillChars, ' ') << arg.Description() << std::endl; + infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; } } } @@ -243,7 +245,7 @@ namespace AppInstaller::CLI std::string helpLink = HelpLink(); if (!helpLink.empty()) { - infoOut << std::endl << Resources::GetInstance().ResolveWingetString(L"HelpLinkPreamble") << ' ' << helpLink << std::endl; + infoOut << std::endl << Resource::String::HelpLinkPreamble << ' ' << helpLink << std::endl; } } @@ -274,7 +276,7 @@ namespace AppInstaller::CLI } // TODO: If we get to a large number of commands, do a fuzzy search much like git - throw CommandException(Resources::GetInstance().ResolveWingetString(L"UnrecognizedCommand"), *itr); + throw CommandException(Resource::String::UnrecognizedCommand, *itr); } // Parse arguments as such: @@ -314,7 +316,7 @@ namespace AppInstaller::CLI if (positionalSearchItr == definedArgs.end()) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"ExtraPositionalError"), currArg); + throw CommandException(Resource::String::ExtraPositionalError, currArg); } execArgs.AddArg(positionalSearchItr->ExecArgType(), currArg); @@ -322,7 +324,7 @@ namespace AppInstaller::CLI // The currentArg must not be empty, and starts with a - else if (currArg.length() == 1) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"InvalidArgumentSpecifierError"), currArg); + throw CommandException(Resource::String::InvalidArgumentSpecifierError, currArg); } // Now it must be at least 2 chars else if (currArg[1] != APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR) @@ -333,7 +335,7 @@ namespace AppInstaller::CLI auto itr = std::find_if(definedArgs.begin(), definedArgs.end(), [&](const Argument& arg) { return (currChar == arg.Alias()); }); if (itr == definedArgs.end()) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"InvalidAliasError"), currArg); + throw CommandException(Resource::String::InvalidAliasError, currArg); } if (itr->Type() == ArgumentType::Flag) @@ -347,11 +349,11 @@ namespace AppInstaller::CLI auto itr2 = std::find_if(definedArgs.begin(), definedArgs.end(), [&](const Argument& arg) { return (currChar == arg.Alias()); }); if (itr2 == definedArgs.end()) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"AdjoinedNotFoundError"), currArg); + throw CommandException(Resource::String::AdjoinedNotFoundError, currArg); } else if (itr2->Type() != ArgumentType::Flag) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"AdjoinedNotFlagError"), currArg); + throw CommandException(Resource::String::AdjoinedNotFlagError, currArg); } else { @@ -367,7 +369,7 @@ namespace AppInstaller::CLI } else { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"SingleCharAfterDashError"), currArg); + throw CommandException(Resource::String::SingleCharAfterDashError, currArg); } } else @@ -375,7 +377,7 @@ namespace AppInstaller::CLI ++incomingArgsItr; if (incomingArgsItr == inv.end()) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"MissingArgumentError"), currArg); + throw CommandException(Resource::String::MissingArgumentError, currArg); } execArgs.AddArg(itr->ExecArgType(), *incomingArgsItr); } @@ -411,7 +413,7 @@ namespace AppInstaller::CLI { if (hasValue) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"FlagContainAdjoinedError"), currArg); + throw CommandException(Resource::String::FlagContainAdjoinedError, currArg); } execArgs.AddArg(arg.ExecArgType()); @@ -425,7 +427,7 @@ namespace AppInstaller::CLI ++incomingArgsItr; if (incomingArgsItr == inv.end()) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"MissingArgumentError"), currArg); + throw CommandException(Resource::String::MissingArgumentError, currArg); } execArgs.AddArg(arg.ExecArgType(), *incomingArgsItr); } @@ -436,7 +438,7 @@ namespace AppInstaller::CLI if (!argFound) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"InvalidNameError"), *incomingArgsItr); + throw CommandException(Resource::String::InvalidNameError, *incomingArgsItr); } } } @@ -472,19 +474,19 @@ namespace AppInstaller::CLI { if (arg.Required() && !execArgs.Contains(arg.ExecArgType())) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"RequiredArgError"), arg.Name()); + throw CommandException(Resource::String::RequiredArgError, arg.Name()); } if (arg.Limit() < execArgs.GetCount(arg.ExecArgType())) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"TooManyArgError"), arg.Name()); + throw CommandException(Resource::String::TooManyArgError, arg.Name()); } } } void Command::ExecuteInternal(Execution::Context& context) const { - context.Reporter.Error() << Resources::GetInstance().ResolveWingetString(L"PendingWorkError") << std::endl; + context.Reporter.Error() << Resource::String::PendingWorkError << std::endl; THROW_HR(E_NOTIMPL); } } diff --git a/src/AppInstallerCLICore/Command.h b/src/AppInstallerCLICore/Command.h @@ -4,6 +4,7 @@ #include "Argument.h" #include "ExecutionContext.h" #include "Invocation.h" +#include "Resources.h" #include <initializer_list> #include <memory> @@ -18,14 +19,16 @@ namespace AppInstaller::CLI { struct CommandException { - CommandException(std::string message, std::string_view param) : m_message(message), m_param(param) {} + // The message should be a localized string, but the parameters are currently not localized. + // We 'convert' the param to a localization independent view here. + CommandException(Resource::LocString message, std::string_view param) : m_message(std::move(message)), m_param(param) {} - const std::string& Message() const { return m_message; } - const std::string_view Param() const { return m_param; } + const Resource::LocString& Message() const { return m_message; } + const Utility::LocIndView Param() const { return m_param; } private: - std::string m_message; - std::string_view m_param; + Resource::LocString m_message; + Utility::LocIndView m_param; }; struct Command @@ -48,8 +51,8 @@ namespace AppInstaller::CLI virtual std::vector<std::unique_ptr<Command>> GetCommands() const { return {}; } virtual std::vector<Argument> GetArguments() const { return {}; } - virtual std::string ShortDescription() const { return {}; } - virtual std::string GetLongDescription() const { return {}; } + virtual Resource::LocString ShortDescription() const { return {}; } + virtual Resource::LocString LongDescription() const { return {}; } virtual void OutputIntroHeader(Execution::Reporter& reporter) const; virtual void OutputHelp(Execution::Reporter& reporter, const CommandException* exception = nullptr) const; diff --git a/src/AppInstallerCLICore/Commands/HashCommand.cpp b/src/AppInstallerCLICore/Commands/HashCommand.cpp @@ -8,6 +8,7 @@ namespace AppInstaller::CLI { using namespace std::string_view_literals; + using namespace Utility::literals; std::vector<Argument> HashCommand::GetArguments() const { @@ -17,14 +18,14 @@ namespace AppInstaller::CLI }; } - std::string HashCommand::ShortDescription() const + Resource::LocString HashCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"HashHelperDescription"); + return { Resource::String::HashCommandShortDescription }; } - std::string HashCommand::GetLongDescription() const + Resource::LocString HashCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"HashHelperDescription"); + return { Resource::String::HashCommandLongDescription }; } std::string HashCommand::HelpLink() const @@ -41,7 +42,7 @@ namespace AppInstaller::CLI auto inputFile = context.Args.GetArg(Execution::Args::Type::HashFile); std::ifstream inStream{ inputFile, std::ifstream::binary }; - context.Reporter.Info() << "File Hash: " + Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHash(inStream)) << std::endl; + context.Reporter.Info() << "Sha256: "_liv << Utility::LocIndString{ Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHash(inStream)) } << std::endl; if (context.Args.Contains(Execution::Args::Type::Msix)) { @@ -51,13 +52,13 @@ namespace AppInstaller::CLI auto signature = msixInfo.GetSignature(); auto signatureHash = Utility::SHA256::ComputeHash(signature.data(), static_cast<uint32_t>(signature.size())); - context.Reporter.Info() << "Signature Hash: " + Utility::SHA256::ConvertToString(signatureHash) << std::endl; + context.Reporter.Info() << "SignatureSha256: "_liv << Utility::LocIndString{ Utility::SHA256::ConvertToString(signatureHash) } << std::endl; } catch (const wil::ResultException& re) { context.Reporter.Warn() << - "Failed to calculate MSIX signature hash." << std::endl << - "Please verify that the input file is a valid, signed MSIX." << std::endl; + Resource::String::MsixSignatureHashFailed << std::endl << + Resource::String::VerifyFileSignedMsix << std::endl; AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); } } diff --git a/src/AppInstallerCLICore/Commands/HashCommand.h b/src/AppInstallerCLICore/Commands/HashCommand.h @@ -11,8 +11,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -36,14 +36,14 @@ namespace AppInstaller::CLI }; } - std::string InstallCommand::ShortDescription() const + Resource::LocString InstallCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"InstallCommandDescription"); + return { Resource::String::InstallCommandShortDescription }; } - std::string InstallCommand::GetLongDescription() const + Resource::LocString InstallCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"InstallCommandDescription"); + return { Resource::String::InstallCommandLongDescription }; } std::string InstallCommand::HelpLink() const @@ -69,7 +69,7 @@ namespace AppInstaller::CLI { if (execArgs.Contains(Execution::Args::Type::Silent) && execArgs.Contains(Execution::Args::Type::Interactive)) { - throw CommandException(Resources::GetInstance().ResolveWingetString(L"TooManyBehaviorsError"), s_InstallCommand_ArgName_SilentAndInteractive); + throw CommandException(Resource::String::TooManyBehaviorsError, s_InstallCommand_ArgName_SilentAndInteractive); } } } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.h b/src/AppInstallerCLICore/Commands/InstallCommand.h @@ -11,8 +11,8 @@ namespace AppInstaller::CLI std::vector<Argument> GetArguments() const override; - std::string ShortDescription() const override; - std::string GetLongDescription() const override; + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; std::string HelpLink() const override; diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -9,10 +9,14 @@ #include "SearchCommand.h" #include "HashCommand.h" #include "ValidateCommand.h" + #include "Resources.h" +#include "TableOutput.h" namespace AppInstaller::CLI { + using namespace Utility::literals; + std::vector<std::unique_ptr<Command>> RootCommand::GetCommands() const { return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({ @@ -29,14 +33,14 @@ namespace AppInstaller::CLI { return { - Argument{ "version", 'v', Execution::Args::Type::ListVersions, Resources::GetInstance().ResolveWingetString(L"ToolVersionArgumentDescription").c_str(), ArgumentType::Flag, Visibility::Help }, - Argument{ "info", APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER, Execution::Args::Type::Info, Resources::GetInstance().ResolveWingetString(L"ToolInfoArgumentDescription").c_str(), ArgumentType::Flag, Visibility::Help }, + Argument{ "version", 'v', Execution::Args::Type::ListVersions, Resource::String::ToolVersionArgumentDescription, ArgumentType::Flag, Visibility::Help }, + Argument{ "info", APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER, Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Visibility::Help }, }; } - std::string RootCommand::GetLongDescription() const + Resource::LocString RootCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"ToolDescription"); + return { Resource::String::ToolDescription }; } std::string RootCommand::HelpLink() const @@ -53,23 +57,27 @@ namespace AppInstaller::CLI auto info = context.Reporter.Info(); info << std::endl << - "Windows: " << Runtime::GetOSVersion() << std::endl; + "Windows: "_liv << Runtime::GetOSVersion() << std::endl; if (Runtime::IsRunningInPackagedContext()) { - info << "Package: " << Runtime::GetPackageVersion() << std::endl; + info << Resource::String::Package << ": "_liv << Runtime::GetPackageVersion() << std::endl; }; - info << std::endl << - "Links:" << std::endl << - " Privacy Statement: https://aka.ms/winget-privacy" << std::endl << - " License agreement: https://aka.ms/winget-license" << std::endl << - " 3rd Party Notices: https://aka.ms/winget-3rdPartyNotice" << std::endl << - " Homepage: https://aka.ms/winget" << std::endl; + info << std::endl; + + Execution::TableOutput<2> links{ context.Reporter, { Resource::LocString(Resource::String::Links).get(), "" } }; + + links.OutputLine({ Resource::LocString(Resource::String::PrivacyStatement).get(), "https://aka.ms/winget-privacy" }); + links.OutputLine({ Resource::LocString(Resource::String::LicenseAgreement).get(), "https://aka.ms/winget-license" }); + links.OutputLine({ Resource::LocString(Resource::String::ThirdPartSoftwareNotices).get(), "https://aka.ms/winget-3rdPartyNotice" }); + links.OutputLine({ Resource::LocString(Resource::String::MainHomepage).get(), "https://aka.ms/winget" }); + + links.Complete(); } else if (context.Args.Contains(Execution::Args::Type::ListVersions)) { - context.Reporter.Info() << 'v' << Runtime::GetClientVersion() << ' ' << Resources::GetInstance().ResolveWingetString(L"PreviewVersion"); + context.Reporter.Info() << 'v' << Runtime::GetClientVersion() << ' ' << Resource::String::PreviewVersion; } else { diff --git a/src/AppInstallerCLICore/Commands/RootCommand.h b/src/AppInstallerCLICore/Commands/RootCommand.h @@ -12,7 +12,7 @@ namespace AppInstaller::CLI std::vector<std::unique_ptr<Command>> GetCommands() const override; std::vector<Argument> GetArguments() const override; - std::string GetLongDescription() const override; + Resource::LocString LongDescription() const override; std::string HelpLink() const override; diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.cpp b/src/AppInstallerCLICore/Commands/SearchCommand.cpp @@ -25,14 +25,14 @@ namespace AppInstaller::CLI }; } - std::string SearchCommand::ShortDescription() const + Resource::LocString SearchCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SearchCommandDescription"); + return { Resource::String::SearchCommandShortDescription }; } - std::string SearchCommand::GetLongDescription() const + Resource::LocString SearchCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SearchCommandDescription").c_str(); + return { Resource::String::SearchCommandLongDescription }; } std::string SearchCommand::HelpLink() const diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.h b/src/AppInstallerCLICore/Commands/SearchCommand.h @@ -11,8 +11,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -24,14 +24,14 @@ namespace AppInstaller::CLI }; } - std::string ShowCommand::ShortDescription() const + Resource::LocString ShowCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"ShowCommandShortDescription"); + return { Resource::String::ShowCommandShortDescription }; } - std::string ShowCommand::GetLongDescription() const + Resource::LocString ShowCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"ShowCommandLongDescription"); + return { Resource::String::ShowCommandLongDescription }; } std::string ShowCommand::HelpLink() const diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.h b/src/AppInstallerCLICore/Commands/ShowCommand.h @@ -11,8 +11,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.cpp b/src/AppInstallerCLICore/Commands/SourceCommand.cpp @@ -24,14 +24,14 @@ namespace AppInstaller::CLI }); } - std::string SourceCommand::ShortDescription() const + Resource::LocString SourceCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceCommandShortDescription"); + return { Resource::String::SourceCommandShortDescription }; } - std::string SourceCommand::GetLongDescription() const + Resource::LocString SourceCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceCommandLongDescription"); + return { Resource::String::SourceCommandLongDescription }; } std::string SourceCommand::HelpLink() const @@ -53,14 +53,14 @@ namespace AppInstaller::CLI }; } - std::string SourceAddCommand::ShortDescription() const + Resource::LocString SourceAddCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceAddCommandShortDescription"); + return { Resource::String::SourceAddCommandShortDescription }; } - std::string SourceAddCommand::GetLongDescription() const + Resource::LocString SourceAddCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceAddCommandLongDescription"); + return { Resource::String::SourceAddCommandLongDescription }; } std::string SourceAddCommand::HelpLink() const @@ -83,14 +83,14 @@ namespace AppInstaller::CLI }; } - std::string SourceListCommand::ShortDescription() const + Resource::LocString SourceListCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceListCommandShortDescription"); + return { Resource::String::SourceListCommandShortDescription }; } - std::string SourceListCommand::GetLongDescription() const + Resource::LocString SourceListCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceListCommandLongDescription"); + return { Resource::String::SourceListCommandLongDescription }; } std::string SourceListCommand::HelpLink() const @@ -112,14 +112,14 @@ namespace AppInstaller::CLI }; } - std::string SourceUpdateCommand::ShortDescription() const + Resource::LocString SourceUpdateCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceUpdateCommandShortDescription"); + return { Resource::String::SourceUpdateCommandShortDescription }; } - std::string SourceUpdateCommand::GetLongDescription() const + Resource::LocString SourceUpdateCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceUpdateCommandLongDescription"); + return { Resource::String::SourceUpdateCommandLongDescription }; } std::string SourceUpdateCommand::HelpLink() const @@ -141,14 +141,14 @@ namespace AppInstaller::CLI }; } - std::string SourceRemoveCommand::ShortDescription() const + Resource::LocString SourceRemoveCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceRemoveCommandShortDescription"); + return { Resource::String::SourceRemoveCommandShortDescription }; } - std::string SourceRemoveCommand::GetLongDescription() const + Resource::LocString SourceRemoveCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceRemoveCommandLongDescription"); + return { Resource::String::SourceRemoveCommandLongDescription }; } std::string SourceRemoveCommand::HelpLink() const @@ -171,14 +171,14 @@ namespace AppInstaller::CLI }; } - std::string SourceResetCommand::ShortDescription() const + Resource::LocString SourceResetCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceResetCommandShortDescription"); + return { Resource::String::SourceResetCommandShortDescription }; } - std::string SourceResetCommand::GetLongDescription() const + Resource::LocString SourceResetCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"SourceResetCommandLongDescription"); + return { Resource::String::SourceResetCommandLongDescription }; } std::string SourceResetCommand::HelpLink() const diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.h b/src/AppInstallerCLICore/Commands/SourceCommand.h @@ -11,8 +11,8 @@ namespace AppInstaller::CLI virtual std::vector<std::unique_ptr<Command>> GetCommands() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; @@ -26,8 +26,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; @@ -41,8 +41,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; @@ -56,8 +56,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; @@ -71,8 +71,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; @@ -86,8 +86,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; diff --git a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp @@ -16,14 +16,14 @@ namespace AppInstaller::CLI }; } - std::string ValidateCommand::ShortDescription() const + Resource::LocString ValidateCommand::ShortDescription() const { - return Resources::GetInstance().ResolveWingetString(L"ValidateCommandShortDescription"); + return Resource::LocString{ Resource::String::ValidateCommandShortDescription }; } - std::string ValidateCommand::GetLongDescription() const + Resource::LocString ValidateCommand::LongDescription() const { - return Resources::GetInstance().ResolveWingetString(L"ValidateCommandLongDescription"); + return Resource::LocString{ Resource::String::ValidateCommandLongDescription }; } std::string ValidateCommand::HelpLink() const @@ -42,17 +42,17 @@ namespace AppInstaller::CLI try { (void)Manifest::Manifest::CreateFromPath(inputFile, true, true); - context.Reporter.Info() << "Manifest validation succeeded." << std::endl; + context.Reporter.Info() << Resource::String::ManifestValidationSuccess << std::endl; } catch (const Manifest::ManifestException& e) { if (e.IsWarningOnly()) { - context.Reporter.Warn() << "Manifest validation succeeded with warnings." << std::endl; + context.Reporter.Warn() << Resource::String::ManifestValidationWarning << std::endl; } else { - context.Reporter.Error() << "Manifest validation failed." << std::endl; + context.Reporter.Error() << Resource::String::ManifestValidationFail << std::endl; } context.Reporter.Info() << e.GetManifestErrorMessage() << std::endl; diff --git a/src/AppInstallerCLICore/Commands/ValidateCommand.h b/src/AppInstallerCLICore/Commands/ValidateCommand.h @@ -11,8 +11,8 @@ namespace AppInstaller::CLI virtual std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::string GetLongDescription() const override; + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp @@ -8,6 +8,7 @@ using namespace winrt; using namespace winrt::Windows::Foundation; using namespace AppInstaller::CLI; +using namespace AppInstaller::Utility::literals; namespace AppInstaller::CLI { @@ -117,7 +118,7 @@ namespace AppInstaller::CLI // Even though they are logged at their source, log again here for completeness. Logging::Telemetry().LogException(command->FullName(), "wil::ResultException", re.what()); context.Reporter.Error() << - "An unexpected error occurred while executing the command: " << std::endl << + Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << GetUserPresentableMessage(re) << std::endl; return re.GetErrorCode(); } @@ -126,7 +127,7 @@ namespace AppInstaller::CLI std::string message = GetUserPresentableMessage(hre); Logging::Telemetry().LogException(command->FullName(), "winrt::hresult_error", message); context.Reporter.Error() << - "An unexpected error occurred while executing the command: " << std::endl << + Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << message << std::endl; return hre.code(); } @@ -134,7 +135,7 @@ namespace AppInstaller::CLI { Logging::Telemetry().LogException(command->FullName(), "std::exception", e.what()); context.Reporter.Error() << - "An unexpected error occurred while executing the command: " << std::endl << + Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << GetUserPresentableMessage(e) << std::endl; return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; } @@ -143,7 +144,7 @@ namespace AppInstaller::CLI LOG_CAUGHT_EXCEPTION(); Logging::Telemetry().LogException(command->FullName(), "unknown", {}); context.Reporter.Error() << - "An unexpected error occurred while executing the command" << std::endl; + Resource::String::UnexpectedErrorExecutingCommand << " ???"_liv << std::endl; return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; } diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h @@ -2,8 +2,10 @@ // Licensed under the MIT License. #pragma once #include "ExecutionProgress.h" +#include "Resources.h" #include "VTSupport.h" #include <AppInstallerProgress.h> +#include <winget/LocIndependent.h> #include <wil/resource.h> @@ -15,6 +17,36 @@ namespace AppInstaller::CLI::Execution { + namespace details + { + // List of approved types for output, others are potentially not localized. + template <typename T> + struct IsApprovedForOutput + { + static constexpr bool value = false; + }; + +#define WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(_t_) \ + template <> \ + struct IsApprovedForOutput<_t_> \ + { \ + static constexpr bool value = true; \ + } + + // It is assumed that single char values need not be localized, as they are matched + // ordinally or they are punctuation / other. + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(char); + // Localized strings (and from an Id for one for convenience). + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Resource::StringId); + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Resource::LocString); + // Strings explicitly declared as localization independent. + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::LocIndView); + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::LocIndString); + // Normalized strings come from user data and should therefore already by localized + // by how they are chosen (or there is no localized version). + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::NormalizedString); + } + // Reporter should be the central place to show workflow status to user. // Todo: need to implement actual console output to show progress bar, etc struct Reporter : public IProgressSink @@ -42,6 +74,20 @@ namespace AppInstaller::CLI::Execution template <typename T> OutputStream& operator<<(const T& t) { + // You've found your way here because you tried to output a type that may not localized. + // In order to ensure that all output is localized, only the types with specializations of + // details::IsApprovedForOutput above can be output. + // * If your string is a simple message, it should be put in + // /src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw + // and referenced in /src/AppInstallerCLICore/Resources.h. Then either output the + // Resource::StringId, or load it manually and output the Resource::LocString. + // * If your string is *definitely* localization independent, you can tag it as such with + // the Utility::LocInd(View/String) types. + // * If your string came from outside of the source code, it is best to store it in a + // Utility::NormalizedString so that it has a normalized representation. This also + // informs the output that there is no localized version to use. + // TODO: Convert the rest of the code base and uncomment to enforce localization. + //static_assert(details::IsApprovedForOutput<std::decay_t<T>>::value, "This type may not be localized, see comment for more information"); ApplyFormat(); m_out << t; return *this; diff --git a/src/AppInstallerCLICore/Resources.cpp b/src/AppInstallerCLICore/Resources.cpp @@ -4,29 +4,28 @@ #include "pch.h" #include "Resources.h" -namespace AppInstaller::CLI + +namespace AppInstaller::CLI::Resource { - const Resources& Resources::GetInstance() + LocString::LocString(StringId id) { - static Resources instance; - return instance; + m_value = Loader::Instance().ResolveString(id); } - Resources::Resources() + const Loader& Loader::Instance() { - m_wingetLoader = winrt::Windows::ApplicationModel::Resources::ResourceLoader::GetForViewIndependentUse(L"winget"); + static Loader instance; + return instance; } - std::string Resources::ResolveWingetString( - std::wstring_view resKey) const + Loader::Loader() { - return Utility::ConvertToUTF8(ResolveWingetWString(resKey)); + m_wingetLoader = winrt::Windows::ApplicationModel::Resources::ResourceLoader::GetForViewIndependentUse(L"winget"); } - std::wstring Resources::ResolveWingetWString( + std::string Loader::ResolveString( std::wstring_view resKey) const { - std::wstring localized(m_wingetLoader.GetString(resKey)); - return localized; + return Utility::ConvertToUTF8(m_wingetLoader.GetString(resKey)); } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -1,29 +1,173 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include <string> #include <winrt/Windows.ApplicationModel.Resources.h> -#include <winrt/Windows.ApplicationModel.Resources.Core.h> -using namespace winrt::Windows::ApplicationModel::Resources; -using namespace winrt::Windows::ApplicationModel::Resources::Core; +#include <iostream> +#include <string> + -namespace AppInstaller::CLI +namespace AppInstaller::CLI::Resource { - class Resources + using namespace std::string_view_literals; + +#define WINGET_WIDE_STRINGIFY_HELP(_id_) L ## _id_ +#define WINGET_WIDE_STRINGIFY(_id_) WINGET_WIDE_STRINGIFY_HELP(_id_) +#define WINGET_DEFINE_RESOURCE_STRINGID(_id_) static constexpr StringId _id_ { WINGET_WIDE_STRINGIFY(#_id_) ## sv } + + // A resource identifier + struct StringId : public std::wstring_view { - public: - static const Resources& GetInstance(); + explicit constexpr StringId(std::wstring_view id) : std::wstring_view(id) {} + }; + + // Resource string identifiers. + // This list can easily be generated by the following PowerShell: + // > [xml]$res = Get-Content <winget.resw file path> + // > $res.root.data.name | % { "WINGET_DEFINE_RESOURCE_STRINGID($_);" } + struct String + { + WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFlagError); + WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFoundError); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguements); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableSubcommands); + WINGET_DEFINE_RESOURCE_STRINGID(ChannelArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Command); + WINGET_DEFINE_RESOURCE_STRINGID(CommandArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); + WINGET_DEFINE_RESOURCE_STRINGID(FileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); + WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HelpArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HelpForDetails); + WINGET_DEFINE_RESOURCE_STRINGID(HelpLinkPreamble); + WINGET_DEFINE_RESOURCE_STRINGID(IdArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer1); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer2); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationRequiresHigherWindows); + WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InteractiveArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidAliasError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentSpecifierError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidNameError); + WINGET_DEFINE_RESOURCE_STRINGID(LanguageArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(LicenseAgreement); + WINGET_DEFINE_RESOURCE_STRINGID(Links); + WINGET_DEFINE_RESOURCE_STRINGID(LocationArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(LogArgumentDesciption); + WINGET_DEFINE_RESOURCE_STRINGID(MainCopyrightNotice); + WINGET_DEFINE_RESOURCE_STRINGID(MainHomepage); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationFail); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationWarning); + WINGET_DEFINE_RESOURCE_STRINGID(MissingArgumentError); + WINGET_DEFINE_RESOURCE_STRINGID(MonikerArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MsixArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); + WINGET_DEFINE_RESOURCE_STRINGID(NameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); + WINGET_DEFINE_RESOURCE_STRINGID(NoVTArguementDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Options); + WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Package); + WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); + WINGET_DEFINE_RESOURCE_STRINGID(PlainArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PreviewVersion); + WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); + WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); + WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SingleCharAfterDashError); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceArgArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceTypeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(TagArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ThirdPartSoftwareNotices); + WINGET_DEFINE_RESOURCE_STRINGID(ToolDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ToolInfoArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ToolVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(TooManyArgError); + WINGET_DEFINE_RESOURCE_STRINGID(TooManyBehaviorsError); + WINGET_DEFINE_RESOURCE_STRINGID(UnexpectedErrorExecutingCommand); + WINGET_DEFINE_RESOURCE_STRINGID(UnrecognizedCommand); + WINGET_DEFINE_RESOURCE_STRINGID(Usage); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateManifestArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VerboseLogsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileSignedMsix); + WINGET_DEFINE_RESOURCE_STRINGID(VersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VersionsArgumentDescription); + }; + + // A localized string + struct LocString + { + LocString() = default; + + LocString(StringId id); - std::string ResolveWingetString( - std::wstring_view resKey) const; + LocString(const LocString&) = default; + LocString& operator=(const LocString&) = default; - std::wstring ResolveWingetWString( - std::wstring_view resKey) const; + LocString(LocString&&) = default; + LocString& operator=(LocString&&) = default; + + const std::string& get() const { return m_value; } + + private: + std::string m_value; + }; + + // Utility class to load resources + class Loader + { + public: + // Gets the singleton instance of the resource loader. + static const Loader& Instance(); + + // Gets the the string resource value. + std::string ResolveString(std::wstring_view resKey) const; private: winrt::Windows::ApplicationModel::Resources::ResourceLoader m_wingetLoader; - Resources(); + Loader(); }; -}- \ No newline at end of file +} + +inline std::ostream& operator<<(std::ostream& out, const AppInstaller::CLI::Resource::LocString& ls) +{ + return (out << ls.get()); +} + +inline std::ostream& operator<<(std::ostream& out, AppInstaller::CLI::Resource::StringId si) +{ + return (out << AppInstaller::CLI::Resource::LocString{ si }); +} diff --git a/src/AppInstallerCLICore/VTSupport.cpp b/src/AppInstallerCLICore/VTSupport.cpp @@ -87,18 +87,18 @@ namespace AppInstaller::CLI::VirtualTerminal { #define AICLI_VT_SIMPLE_CURSORPOSITON(_c_) AICLI_VT_ESCAPE #_c_ - const Sequence UpOne = AICLI_VT_SIMPLE_CURSORPOSITON(A); - const Sequence DownOne = AICLI_VT_SIMPLE_CURSORPOSITON(B); - const Sequence ForwardOne = AICLI_VT_SIMPLE_CURSORPOSITON(C); - const Sequence BackwardOne = AICLI_VT_SIMPLE_CURSORPOSITON(D); + const Sequence UpOne{ AICLI_VT_SIMPLE_CURSORPOSITON(A) }; + const Sequence DownOne{ AICLI_VT_SIMPLE_CURSORPOSITON(B) }; + const Sequence ForwardOne{ AICLI_VT_SIMPLE_CURSORPOSITON(C) }; + const Sequence BackwardOne{ AICLI_VT_SIMPLE_CURSORPOSITON(D) }; } namespace Visibility { - const Sequence EnableBlink = AICLI_VT_CSI "?12h"; - const Sequence DisableBlink = AICLI_VT_CSI "?12l"; - const Sequence EnableShow = AICLI_VT_CSI "?25h"; - const Sequence DisableShow = AICLI_VT_CSI "?25l"; + const Sequence EnableBlink{ AICLI_VT_CSI "?12h" }; + const Sequence DisableBlink{ AICLI_VT_CSI "?12l" }; + const Sequence EnableShow{ AICLI_VT_CSI "?25h" }; + const Sequence DisableShow{ AICLI_VT_CSI "?25l" }; } } @@ -107,30 +107,30 @@ namespace AppInstaller::CLI::VirtualTerminal // Define a text formatting sequence with an integer id #define AICLI_VT_TEXTFORMAT(_id_) AICLI_VT_CSI #_id_ "m" - const Sequence Default = AICLI_VT_TEXTFORMAT(0); - const Sequence Negative = AICLI_VT_TEXTFORMAT(7); + const Sequence Default{ AICLI_VT_TEXTFORMAT(0) }; + const Sequence Negative{ AICLI_VT_TEXTFORMAT(7) }; Color Color::GetAccentColor() { - static Color accent = GetAccentColorFromSystem(); + static Color accent{ GetAccentColorFromSystem() }; return accent; } namespace Foreground { - const Sequence BrightRed = AICLI_VT_TEXTFORMAT(91); - const Sequence BrightGreen = AICLI_VT_TEXTFORMAT(92); - const Sequence BrightYellow = AICLI_VT_TEXTFORMAT(93); - const Sequence BrightBlue = AICLI_VT_TEXTFORMAT(94); - const Sequence BrightMagenta = AICLI_VT_TEXTFORMAT(95); - const Sequence BrightCyan = AICLI_VT_TEXTFORMAT(96); - const Sequence BrightWhite = AICLI_VT_TEXTFORMAT(97); + const Sequence BrightRed{ AICLI_VT_TEXTFORMAT(91) }; + const Sequence BrightGreen{ AICLI_VT_TEXTFORMAT(92) }; + const Sequence BrightYellow{ AICLI_VT_TEXTFORMAT(93) }; + const Sequence BrightBlue{ AICLI_VT_TEXTFORMAT(94) }; + const Sequence BrightMagenta{ AICLI_VT_TEXTFORMAT(95) }; + const Sequence BrightCyan{ AICLI_VT_TEXTFORMAT(96) }; + const Sequence BrightWhite{ AICLI_VT_TEXTFORMAT(97) }; ConstructedSequence Extended(const Color& color) { std::ostringstream result; result << AICLI_VT_CSI "38;2;" << static_cast<uint32_t>(color.R) << ';' << static_cast<uint32_t>(color.G) << ';' << static_cast<uint32_t>(color.B) << 'm'; - return result.str(); + return ConstructedSequence{ result.str() }; } } @@ -142,8 +142,8 @@ namespace AppInstaller::CLI::VirtualTerminal namespace TextModification { - const Sequence EraseLineForward = AICLI_VT_CSI "0K"; - const Sequence EraseLineBackward = AICLI_VT_CSI "1K"; - const Sequence EraseLineEntirely = AICLI_VT_CSI "2K"; + const Sequence EraseLineForward{ AICLI_VT_CSI "0K" }; + const Sequence EraseLineBackward{ AICLI_VT_CSI "1K" }; + const Sequence EraseLineEntirely{ AICLI_VT_CSI "2K" }; } } diff --git a/src/AppInstallerCLICore/VTSupport.h b/src/AppInstallerCLICore/VTSupport.h @@ -36,7 +36,7 @@ namespace AppInstaller::CLI::VirtualTerminal struct Sequence { Sequence() = default; - Sequence(const char* c) : m_chars(c) {} + explicit Sequence(const char* c) : m_chars(c) {} const char* Get() const { return m_chars; } @@ -50,7 +50,7 @@ namespace AppInstaller::CLI::VirtualTerminal // A VT sequence that is constructed at runtime. struct ConstructedSequence : public Sequence { - ConstructedSequence(std::string s) : m_str(std::move(s)) { Set(m_str); } + explicit ConstructedSequence(std::string s) : m_str(std::move(s)) { Set(m_str); } private: std::string m_str; diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -20,7 +20,7 @@ namespace AppInstaller::CLI::Workflow if (!manifest.MinOSVersion.empty() && !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Version(manifest.MinOSVersion))) { - context.Reporter.Error() << "Cannot install application, as it requires a higher OS version: " << manifest.MinOSVersion << std::endl; + context.Reporter.Error() << Resource::String::InstallationRequiresHigherWindows << ' ' << manifest.MinOSVersion << std::endl; AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION)); } } @@ -31,7 +31,7 @@ namespace AppInstaller::CLI::Workflow if (!installer.has_value()) { - context.Reporter.Error() << "No installers are applicable to the current system" << std::endl; + context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); } } @@ -39,8 +39,8 @@ namespace AppInstaller::CLI::Workflow void ShowInstallationDisclaimer(Execution::Context& context) { context.Reporter.Info() << - "This application is licensed to you by its owner." << std::endl << - "Microsoft is not responsible for, nor does it grant any licenses to, third-party packages." << std::endl; + Resource::String::InstallationDisclaimer1 << std::endl << + Resource::String::InstallationDisclaimer2 << std::endl; } void DownloadInstaller(Execution::Context& context) diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h @@ -46,3 +46,4 @@ #include <AppInstallerTelemetry.h> #include <Manifest/ManifestInstaller.h> #include <Manifest/Manifest.h> +#include <winget/LocIndependent.h> diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -54,7 +54,7 @@ namespace AppInstallerCLIE2ETests installDir = TestCommon.GetRandomTestDir(); result = TestCommon.RunAICLICommand("install", $"InapplicableOsVersion --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.ERROR_OLD_WIN_VERSION, result.ExitCode); - Assert.True(result.StdOut.Contains("Cannot install application, as it requires a higher OS version")); + Assert.True(result.StdOut.Contains("Cannot install application, as it requires a higher version of Windows")); Assert.False(VerifyTestExeInstalled(installDir)); // Install test exe but hash mismatch, passing N should cause the installation to fail diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -140,6 +140,10 @@ <data name="ChannelArgumentDescription" xml:space="preserve"> <value>Use the specified channel; default is general audience</value> </data> + <data name="Command" xml:space="preserve"> + <value>command</value> + <comment>A command to give the software</comment> + </data> <data name="CommandArgumentDescription" xml:space="preserve"> <value>Filter results by command</value> </data> @@ -161,7 +165,10 @@ <data name="ForceArgumentDescription" xml:space="preserve"> <value>Execute the command without prompts</value> </data> - <data name="HashHelperDescription" xml:space="preserve"> + <data name="HashCommandLongDescription" xml:space="preserve"> + <value>Computes the hash of a local file, appropriate for entry into a manifest. It can also compute the hash of the signature file of an MSIX package to enable streaming installations.</value> + </data> + <data name="HashCommandShortDescription" xml:space="preserve"> <value>Helper to hash installer files</value> </data> <data name="HelpArgumentDescription" xml:space="preserve"> @@ -176,7 +183,19 @@ <data name="IdArgumentDescription" xml:space="preserve"> <value>Filter results by id</value> </data> - <data name="InstallCommandDescription" xml:space="preserve"> + <data name="InstallationDisclaimer1" xml:space="preserve"> + <value>This application is licensed to you by its owner.</value> + </data> + <data name="InstallationDisclaimer2" xml:space="preserve"> + <value>Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.</value> + </data> + <data name="InstallationRequiresHigherWindows" xml:space="preserve"> + <value>Cannot install application, as it requires a higher version of Windows:</value> + </data> + <data name="InstallCommandLongDescription" xml:space="preserve"> + <value>Installs the selected application, either found by searching a configured source or directly from a manifest.</value> + </data> + <data name="InstallCommandShortDescription" xml:space="preserve"> <value>Installs the given application</value> </data> <data name="InteractiveArgumentDescription" xml:space="preserve"> @@ -194,15 +213,38 @@ <data name="LanguageArgumentDescription" xml:space="preserve"> <value>Language to install (if supported)</value> </data> + <data name="LicenseAgreement" xml:space="preserve"> + <value>License Agreement</value> + </data> + <data name="Links" xml:space="preserve"> + <value>Links</value> + <comment>Links to different webpages</comment> + </data> <data name="LocationArgumentDescription" xml:space="preserve"> <value>Location to install to (if supported)</value> </data> <data name="LogArgumentDesciption" xml:space="preserve"> <value>Log location (if supported)</value> </data> + <data name="MainCopyrightNotice" xml:space="preserve"> + <value>Copyright (c) Microsoft Corporation. All rights reserved.</value> + </data> + <data name="MainHomepage" xml:space="preserve"> + <value>Homepage</value> + <comment>The primary webpage for the software</comment> + </data> <data name="ManifestArgumentDescription" xml:space="preserve"> <value>The path to the manifest of the application</value> </data> + <data name="ManifestValidationFail" xml:space="preserve"> + <value>Manifest validation failed.</value> + </data> + <data name="ManifestValidationSuccess" xml:space="preserve"> + <value>Manifest validation succeeded.</value> + </data> + <data name="ManifestValidationWarning" xml:space="preserve"> + <value>Manifest validation succeeded with warnings.</value> + </data> <data name="MissingArgumentError" xml:space="preserve"> <value>Argument value required, but none found</value> </data> @@ -212,16 +254,30 @@ <data name="MsixArgumentDescription" xml:space="preserve"> <value>Input file will be treated as msix; signature hash will be provided if signed</value> </data> + <data name="MsixSignatureHashFailed" xml:space="preserve"> + <value>Failed to calculate MSIX signature hash.</value> + </data> <data name="NameArgumentDescription" xml:space="preserve"> <value>Filter results by name</value> </data> + <data name="NoApplicableInstallers" xml:space="preserve"> + <value>No installers are applicable to the current system.</value> + </data> <data name="NoVTArguementDescription" xml:space="preserve"> <value>Disables VirtualTerminal display</value> <comment>{Locked="VirtualTerminal"}</comment> </data> + <data name="Options" xml:space="preserve"> + <value>options</value> + <comment>Options to change how a command works</comment> + </data> <data name="OverrideArgumentDescription" xml:space="preserve"> <value>Override arguments to be passed on to the installer</value> </data> + <data name="Package" xml:space="preserve"> + <value>Package</value> + <comment>A software package</comment> + </data> <data name="PendingWorkError" xml:space="preserve"> <value>Oops, we forgot to do this...</value> </data> @@ -231,6 +287,9 @@ <data name="PreviewVersion" xml:space="preserve"> <value>Preview</value> </data> + <data name="PrivacyStatement" xml:space="preserve"> + <value>Privacy Statement</value> + </data> <data name="QueryArgumentDescription" xml:space="preserve"> <value>The query used to search for an app</value> </data> @@ -240,7 +299,10 @@ <data name="RequiredArgError" xml:space="preserve"> <value>Required argument not provided</value> </data> - <data name="SearchCommandDescription" xml:space="preserve"> + <data name="SearchCommandLongDescription" xml:space="preserve"> + <value>Searches for applications from configured sources.</value> + </data> + <data name="SearchCommandShortDescription" xml:space="preserve"> <value>Find and show basic info of apps</value> </data> <data name="ShowCommandLongDescription" xml:space="preserve"> @@ -306,6 +368,9 @@ <data name="TagArgumentDescription" xml:space="preserve"> <value>Filter results by tag</value> </data> + <data name="ThirdPartSoftwareNotices" xml:space="preserve"> + <value>Third Party Notices</value> + </data> <data name="ToolDescription" xml:space="preserve"> <value>WinGet command line utility enables installing applications from the command line.</value> </data> @@ -321,9 +386,16 @@ <data name="TooManyBehaviorsError" xml:space="preserve"> <value>More than one install behavior argument provided</value> </data> + <data name="UnexpectedErrorExecutingCommand" xml:space="preserve"> + <value>An unexpected error occurred while executing the command:</value> + </data> <data name="UnrecognizedCommand" xml:space="preserve"> <value>Unrecognized command</value> </data> + <data name="Usage" xml:space="preserve"> + <value>usage</value> + <comment>The way to use the software</comment> + </data> <data name="ValidateCommandLongDescription" xml:space="preserve"> <value>Validates a manifest using a strict set of guidelines. This is intended to enable you to check your manifest before submitting to a repo.</value> </data> @@ -336,6 +408,9 @@ <data name="VerboseLogsArgumentDescription" xml:space="preserve"> <value>Enables verbose logging for WinGet</value> </data> + <data name="VerifyFileSignedMsix" xml:space="preserve"> + <value>Please verify that the input file is a valid, signed MSIX.</value> + </data> <data name="VersionArgumentDescription" xml:space="preserve"> <value>Use the specified version; default is the latest version</value> </data> diff --git a/src/AppInstallerCLITests/Command.cpp b/src/AppInstallerCLITests/Command.cpp @@ -7,6 +7,7 @@ #include <Commands/RootCommand.h> using namespace std::string_literals; +using namespace std::string_view_literals; using namespace TestCommon; using namespace AppInstaller; using namespace AppInstaller::CLI; @@ -162,13 +163,16 @@ void RequireValueParsedToArg(const std::string& value, const Argument& arg, cons REQUIRE(value == args.GetArg(arg.ExecArgType())); } +// Description used for tests; doesn't need to be anything in particular. +static constexpr Resource::StringId DefaultDesc{ L""sv }; + TEST_CASE("ParseArguments_MultiplePositional", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, }); std::vector<std::string> values{ "val1", "val2" }; @@ -184,9 +188,9 @@ TEST_CASE("ParseArguments_ForcePositional", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, }); std::vector<std::string> values{ "val1", "--", "-std1" }; @@ -202,8 +206,8 @@ TEST_CASE("ParseArguments_TooManyPositional", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, }); std::vector<std::string> values{ "val1", "--", "-std1" }; @@ -216,9 +220,9 @@ TEST_CASE("ParseArguments_InvalidChar", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, }); std::vector<std::string> values{ "val1", "-", "-std1" }; @@ -231,9 +235,9 @@ TEST_CASE("ParseArguments_InvalidAlias", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, }); std::vector<std::string> values{ "val1", "-b", "-std1" }; @@ -246,10 +250,10 @@ TEST_CASE("ParseArguments_MultiFlag", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "flag1", 's', Args::Type::Command, "", ArgumentType::Flag }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, - Argument{ "flag2", 't', Args::Type::Exact, "", ArgumentType::Flag }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, }); std::vector<std::string> values{ "val1", "-st", "val2" }; @@ -265,10 +269,10 @@ TEST_CASE("ParseArguments_FlagThenUnknown", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "flag1", 's', Args::Type::Command, "", ArgumentType::Flag }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, - Argument{ "flag2", 't', Args::Type::Exact, "", ArgumentType::Flag }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, }); std::vector<std::string> values{ "val1", "-sr", "val2" }; @@ -281,10 +285,10 @@ TEST_CASE("ParseArguments_FlagThenNonFlag", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "flag1", 's', Args::Type::Command, "", ArgumentType::Flag }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, - Argument{ "flag2", 't', Args::Type::Exact, "", ArgumentType::Flag }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, }); std::vector<std::string> values{ "val1", "-sp", "val2" }; @@ -297,10 +301,10 @@ TEST_CASE("ParseArguments_NameUsingAliasSpecifier", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, "", ArgumentType::Flag }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, }); std::vector<std::string> values{ "another", "-flag1" }; @@ -313,9 +317,9 @@ TEST_CASE("ParseArguments_AliasWithAdjoinedValue", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, }); std::vector<std::string> values{ "-s=Val1" }; @@ -330,9 +334,9 @@ TEST_CASE("ParseArguments_AliasWithSeparatedValue", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, }); std::vector<std::string> values{ "-s", "Val1" }; @@ -347,9 +351,9 @@ TEST_CASE("ParseArguments_AliasWithSeparatedValueMissing", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, }); std::vector<std::string> values{ "-s" }; @@ -362,9 +366,9 @@ TEST_CASE("ParseArguments_NameWithAdjoinedValue", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, }); std::vector<std::string> values{ "--pos1=Val1" }; @@ -379,10 +383,10 @@ TEST_CASE("ParseArguments_NameFlag", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, "", ArgumentType::Flag }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, }); std::vector<std::string> values{ "--flag1", "arbitrary" }; @@ -398,10 +402,10 @@ TEST_CASE("ParseArguments_NameFlagWithAdjoinedValue", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, "", ArgumentType::Flag }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, }); std::vector<std::string> values{ "another", "--flag1=arbitrary" }; @@ -414,10 +418,10 @@ TEST_CASE("ParseArguments_NameWithSeparatedValue", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, "", ArgumentType::Flag }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, }); std::vector<std::string> values{ "--pos2", "arbitrary" }; @@ -432,10 +436,10 @@ TEST_CASE("ParseArguments_UnknownName", "[command]") { Args args; TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, "", ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, "", ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, "", ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, "", ArgumentType::Flag }, + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, }); std::vector<std::string> values{ "another", "--nope" }; diff --git a/src/AppInstallerCLITests/HashCommand.cpp b/src/AppInstallerCLITests/HashCommand.cpp @@ -18,6 +18,6 @@ TEST_CASE("HashCommandWithTestMsix", "[Sha256Hash]") hashCommand.Execute(context); - REQUIRE(hashOutput.str().find("File Hash: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea") != std::string::npos); - REQUIRE(hashOutput.str().find("Signature Hash: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07") != std::string::npos); + REQUIRE(hashOutput.str().find("Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea") != std::string::npos); + REQUIRE(hashOutput.str().find("SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07") != std::string::npos); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/SQLiteIndexSource.cpp b/src/AppInstallerCLITests/SQLiteIndexSource.cpp @@ -87,7 +87,7 @@ TEST_CASE("SQLiteIndexSource_Id", "[sqliteindexsource]") REQUIRE(results.Matches[0].Application); IApplication* app = results.Matches[0].Application.get(); - REQUIRE(app->GetId() == manifest.Id); + REQUIRE(app->GetId().get() == manifest.Id); } TEST_CASE("SQLiteIndexSource_Name", "[sqliteindexsource]") @@ -108,7 +108,7 @@ TEST_CASE("SQLiteIndexSource_Name", "[sqliteindexsource]") REQUIRE(results.Matches[0].Application); IApplication* app = results.Matches[0].Application.get(); - REQUIRE(app->GetName() == manifest.Name); + REQUIRE(app->GetName().get() == manifest.Name); } TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -14,6 +14,7 @@ #include <Public/AppInstallerRepositorySearch.h> #include <Commands/InstallCommand.h> #include <Commands/ShowCommand.h> +#include <winget/LocIndependent.h> using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Management::Deployment; @@ -41,14 +42,14 @@ struct TestSource : public ISource return m_manifest; } - std::string GetId() override + LocIndString GetId() override { - return m_manifest.Id; + return LocIndString{ m_manifest.Id }; } - std::string GetName() override + LocIndString GetName() override { - return m_manifest.Name; + return LocIndString{ m_manifest.Name }; } std::vector<VersionAndChannel> GetVersions() override diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -193,6 +193,7 @@ <ClInclude Include="Public\AppInstallerArchitecture.h" /> <ClInclude Include="Public\AppInstallerVersions.h" /> <ClInclude Include="Public\winget\ExtensionCatalog.h" /> + <ClInclude Include="Public\winget\LocIndependent.h" /> <ClInclude Include="Telemetry\MicrosoftTelemetry.h" /> <ClInclude Include="Telemetry\TraceLogging.h" /> <ClInclude Include="Telemetry\WinEventLogLevels.h" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -99,6 +99,9 @@ <ClInclude Include="Public\winget\ExtensionCatalog.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\LocIndependent.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -28,6 +28,8 @@ std::ostream& operator<<(std::ostream& out, const GUID& guid) namespace AppInstaller::Logging { + using namespace Utility; + namespace { void __stdcall wilResultLoggingCallback(const wil::FailureInfo& info) noexcept @@ -94,8 +96,8 @@ namespace AppInstaller::Logging void TelemetryTraceLogger::LogStartup() noexcept { - std::string version = Runtime::GetClientVersion(); - std::string packageVersion; + LocIndString version = Runtime::GetClientVersion(); + LocIndString packageVersion; if (Runtime::IsRunningInPackagedContext()) { packageVersion = Runtime::GetPackageVersion(); @@ -107,8 +109,8 @@ namespace AppInstaller::Logging "ClientStartup", GetActivityId(), nullptr, - TraceLoggingCountedString(version.c_str(), static_cast<ULONG>(version.size()), "Version"), - TraceLoggingCountedString(packageVersion.c_str(), static_cast<ULONG>(packageVersion.size()), "PackageVersion"), + TraceLoggingCountedString(version->c_str(), static_cast<ULONG>(version->size()), "Version"), + TraceLoggingCountedString(packageVersion->c_str(), static_cast<ULONG>(packageVersion->size()), "PackageVersion"), TraceLoggingWideString(GetCommandLineW(), "CommandlineArgs"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance|PDT_ProductAndServiceUsage), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include <AppInstallerVersions.h> +#include <winget/LocIndependent.h> #include <filesystem> #include <memory> @@ -14,13 +15,13 @@ namespace AppInstaller::Runtime bool IsRunningInPackagedContext(); // Determines the current version of the client and returns it. - std::string GetClientVersion(); + Utility::LocIndString GetClientVersion(); // Determines the current version of the package if running in a packaged context. - std::string GetPackageVersion(); + Utility::LocIndString GetPackageVersion(); // Gets a string representation of the OS version for debugging purposes. - std::string GetOSVersion(); + Utility::LocIndString GetOSVersion(); // Gets the path to the temp location. std::filesystem::path GetPathToTemp(); diff --git a/src/AppInstallerCommonCore/Public/winget/LocIndependent.h b/src/AppInstallerCommonCore/Public/winget/LocIndependent.h @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <string> +#include <string_view> + +namespace AppInstaller::Utility +{ + // "I solemnly swear that this string is indeed localization independent." + // A localization independent string view. + // Used as a wrapper around strings that do not need localization. + struct LocIndView : public std::string_view + { + explicit constexpr LocIndView(std::string_view sv) : std::string_view(sv) {} + }; + + namespace literals + { + // "I solemnly swear that this string is indeed localization independent." + // Enable easier use of a localization independent view through literals. + inline LocIndView operator ""_liv(const char* chars, size_t size) + { + return LocIndView{ std::string_view{ chars, size } }; + } + } + + // "I solemnly swear that this string is indeed localization independent." + // A localization independent string; either through external localization + // or by virtue of not needing to be localized. + // Intentionally does not allow the value to be modified to prevent accidental + // reintroduction of a localization dependent value. + struct LocIndString + { + LocIndString() = default; + + explicit LocIndString(std::string v) : m_value(std::move(v)) {} + + LocIndString(const LocIndString&) = default; + LocIndString& operator=(const LocIndString&) = default; + + LocIndString(LocIndString&&) = default; + LocIndString& operator=(LocIndString&&) = default; + + const std::string& get() const { return m_value; } + + operator const std::string& () const { return m_value; } + operator std::string_view() const { return m_value; } + + const std::string* operator->() const { return &m_value; } + + bool operator==(std::string_view sv) { return m_value == sv; } + + private: + std::string m_value; + }; +} + +inline std::ostream& operator<<(std::ostream& out, const AppInstaller::Utility::LocIndString& lis) +{ + return (out << lis.get()); +} diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp @@ -10,6 +10,8 @@ namespace AppInstaller::Runtime { + using namespace Utility; + namespace { using namespace std::string_view_literals; @@ -181,34 +183,38 @@ namespace AppInstaller::Runtime return result; } - std::string GetClientVersion() + LocIndString GetClientVersion() { using namespace std::string_literals; + // Major and minor come directly from version.h + std::ostringstream strstr; + strstr << VERSION_MAJOR << '.' << VERSION_MINOR << '.'; + + // Build comes from the package for now, if packaged. if (IsRunningInPackagedContext()) { auto version = GetPACKAGE_VERSION(); if (!version) { - return "error"s; + // In the extremely unlikely event of a failure, this is merely a senitinel value + // to indicated such. The only other option is to completely prevent execution, + // which seems unnecessary. + return LocIndString{ "error" }; } - std::ostringstream strstr; - strstr << VERSION_MAJOR << '.' << VERSION_MINOR << '.' << version->Build; - - return strstr.str(); + strstr << version->Build; } else { - std::ostringstream strstr; - strstr << VERSION_MAJOR << '.' << VERSION_MINOR << '.' << VERSION_BUILD; - - return strstr.str(); + strstr << VERSION_BUILD; } + + return LocIndString{ strstr.str() }; } - std::string GetPackageVersion() + LocIndString GetPackageVersion() { using namespace std::string_literals; @@ -218,21 +224,25 @@ namespace AppInstaller::Runtime if (!version) { - return "error"s; + // In the extremely unlikely event of a failure, this is merely a senitinel value + // to indicated such. The only other option is to completely prevent execution, + // which seems unnecessary. + return LocIndString{ "error" }; } std::ostringstream strstr; strstr << GetPackageName() << " v" << version->Major << '.' << version->Minor << '.' << version->Build << '.' << version->Revision; - return strstr.str(); + return LocIndString{ strstr.str() }; } else { - return "none"; + // Calling code should avoid calling in when this is the case. + return LocIndString{ "none" }; } } - std::string GetOSVersion() + LocIndString GetOSVersion() { winrt::Windows::System::Profile::AnalyticsInfo analyticsInfo{}; auto versionInfo = analyticsInfo.VersionInfo(); @@ -249,7 +259,7 @@ namespace AppInstaller::Runtime std::ostringstream strstr; strstr << Utility::ConvertToUTF8(versionInfo.DeviceFamily()) << " v" << parts[3] << '.' << parts[2] << '.' << parts[1] << '.' << parts[0]; - return strstr.str(); + return LocIndString{ strstr.str() }; } std::filesystem::path GetPathToTemp() diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -7,6 +7,8 @@ namespace AppInstaller::Repository::Microsoft { + using namespace Utility; + namespace { // The IApplication impl for SQLiteIndexSource. @@ -16,14 +18,16 @@ namespace AppInstaller::Repository::Microsoft m_id(id), m_source(source) {} // Inherited via IApplication - std::string GetId() override + LocIndString GetId() override { - return GetSource()->GetIndex().GetIdStringById(m_id).value(); + // Values coming from the index will always be localized/independent. + return LocIndString{ GetSource()->GetIndex().GetIdStringById(m_id).value() }; } - std::string GetName() override + LocIndString GetName() override { - return GetSource()->GetIndex().GetNameStringById(m_id).value(); + // Values coming from the index will always be localized/independent. + return LocIndString{ GetSource()->GetIndex().GetNameStringById(m_id).value() }; } std::optional<Manifest::Manifest> GetManifest(const Utility::NormalizedString& version, const Utility::NormalizedString& channel) override diff --git a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h @@ -4,6 +4,7 @@ #include <Manifest/Manifest.h> #include <AppInstallerStrings.h> #include <AppInstallerVersions.h> +#include <winget/LocIndependent.h> #include <memory> #include <optional> @@ -75,10 +76,10 @@ namespace AppInstaller::Repository virtual ~IApplication() = default; // Gets the id of the application. - virtual std::string GetId() = 0; + virtual Utility::LocIndString GetId() = 0; // Gets the name of the application (the latest name). - virtual std::string GetName() = 0; + virtual Utility::LocIndString GetName() = 0; // Gets a manifest for this application. // An empty version implies 'latest'.