Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More localization friendly source strings and context commenting #2454

Merged
merged 60 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
3ddffad
Added join, toString and format
AmelBawa-msft Aug 11, 2022
b1af993
Moved Loc to common resources
AmelBawa-msft Aug 11, 2022
6a6f5ae
Added /MP
AmelBawa-msft Aug 11, 2022
47a9983
Added operator() for StringId
AmelBawa-msft Aug 11, 2022
5a16227
Removed CommandException cstr with param
AmelBawa-msft Aug 11, 2022
6a066cf
Removed CommandException with placeholder
AmelBawa-msft Aug 11, 2022
2153425
Removed m_replace
AmelBawa-msft Aug 11, 2022
442ce14
Use placeholders
AmelBawa-msft Aug 12, 2022
cb262e2
Fixed build errors
AmelBawa-msft Aug 12, 2022
fd92fef
Updated resources and tests
AmelBawa-msft Aug 12, 2022
cdd4f47
Updated resw
AmelBawa-msft Aug 12, 2022
aafa6b7
Fixed dependencies test
AmelBawa-msft Aug 12, 2022
d9d5197
Moved loader to cpp
AmelBawa-msft Aug 12, 2022
e540363
Moved functions in same file
AmelBawa-msft Aug 12, 2022
dbb5568
Added more placeholders
AmelBawa-msft Aug 12, 2022
49c79a7
Simplify command
AmelBawa-msft Aug 12, 2022
76b7845
Revert changes for ShowFlow.cpp
AmelBawa-msft Aug 12, 2022
3aa289b
Revert ShowFlow.cpp resw data
AmelBawa-msft Aug 12, 2022
2a1a142
Added type constraints
AmelBawa-msft Aug 13, 2022
6fc16bc
Updated resw for WPM
AmelBawa-msft Aug 13, 2022
0eefc3e
Init resw comments
AmelBawa-msft Aug 13, 2022
cf5111d
resw typo
AmelBawa-msft Aug 13, 2022
97ee31b
Fixed resw syntax + added downloading
AmelBawa-msft Aug 14, 2022
ca67eda
Added Test
AmelBawa-msft Aug 14, 2022
1428e12
Revert /MP
AmelBawa-msft Aug 15, 2022
54eff4f
Simplified loc strings
AmelBawa-msft Aug 15, 2022
19870fe
Updated common channel stream code
AmelBawa-msft Aug 15, 2022
d607717
Updated loc in command
AmelBawa-msft Aug 16, 2022
02d357b
Updated loc in more commands
AmelBawa-msft Aug 16, 2022
637fa28
Updated loc in more commands
AmelBawa-msft Aug 16, 2022
79ce892
Resolved conflicts
AmelBawa-msft Aug 16, 2022
1ee37cd
Fix typos and format
AmelBawa-msft Aug 16, 2022
0de00d0
Revert ToString
AmelBawa-msft Aug 16, 2022
aa81820
Added TODO
AmelBawa-msft Aug 16, 2022
873cc08
Addressed comments
AmelBawa-msft Aug 16, 2022
71ac74c
Addressed comment + renamed header file
AmelBawa-msft Aug 16, 2022
57afaa8
Addressed comments
AmelBawa-msft Aug 17, 2022
1c754a5
Addressed comments
AmelBawa-msft Aug 17, 2022
b71b4fa
Resolved comments
AmelBawa-msft Aug 17, 2022
1f5a9ea
Addressed comments
AmelBawa-msft Aug 18, 2022
adfedbd
Addressed comments
AmelBawa-msft Aug 20, 2022
eb4b47f
Addressed comment
AmelBawa-msft Aug 23, 2022
e7c70e6
Addressed comment
AmelBawa-msft Aug 23, 2022
192830e
Fixed build
AmelBawa-msft Nov 14, 2022
233ae44
Resolving conflicts
AmelBawa-msft Nov 14, 2022
5a64fa1
Resolving conflicts
AmelBawa-msft Nov 18, 2022
a66d057
Update string resource
AmelBawa-msft Nov 18, 2022
cb9bc9f
Revert minor code change
AmelBawa-msft Nov 18, 2022
84a26cc
Merge branch 'master' of https://github.com/AmelBawa-msft/winget-cli …
AmelBawa-msft Dec 8, 2022
92c5db5
Resolved conflict
AmelBawa-msft Dec 9, 2022
8b14f8d
Comment typo
AmelBawa-msft Dec 9, 2022
01b39b7
Addressed comments
AmelBawa-msft Dec 14, 2022
ff66049
Addressed comments
AmelBawa-msft Dec 14, 2022
55da0a3
Addressed comments
AmelBawa-msft Dec 14, 2022
b2b98b1
Resolved conflict
AmelBawa-msft Dec 14, 2022
a192d9f
Addressed comments
AmelBawa-msft Dec 15, 2022
fcb963b
Addressed comments
AmelBawa-msft Dec 15, 2022
e1eb803
Updated root command labels
AmelBawa-msft Dec 15, 2022
8355a1c
Resolved conflict
AmelBawa-msft Dec 15, 2022
c3cbf4e
Resolved conflict
AmelBawa-msft Dec 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 2 additions & 31 deletions src/AppInstallerCLICore/ChannelStreams.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,6 @@

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);
}

// The base stream for all channels.
struct BaseStream
{
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -102,7 +72,8 @@ namespace AppInstaller::CLI::Execution
// * 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.
// TODO: This assertion is currently only applied to placeholders in localized strings.
// 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");
if (m_enabled)
{
Expand Down
94 changes: 31 additions & 63 deletions src/AppInstallerCLICore/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,7 @@ using namespace AppInstaller::Settings;

namespace AppInstaller::CLI
{
constexpr std::string_view s_Command_ArgName_SilentAndInteractive = "silent|interactive"sv;

const Utility::LocIndString CommandException::Message() const
{
if (m_replace)
{
return Utility::LocIndString{ Utility::FindAndReplaceMessageToken(m_message, m_replace.value()) };
}

// Fall back to just using the message.
return Utility::LocIndString{ m_message.get() };
}
constexpr Utility::LocIndView s_Command_ArgName_SilentAndInteractive = "silent|interactive"_liv;

Command::Command(
std::string_view name,
Expand All @@ -49,9 +38,8 @@ namespace AppInstaller::CLI

void Command::OutputIntroHeader(Execution::Reporter& reporter) const
{
reporter.Info() <<
(Runtime::IsReleaseBuild() ? Resource::String::WindowsPackageManager : Resource::String::WindowsPackageManagerPreview) << " v"_liv << Runtime::GetClientVersion() << std::endl <<
Resource::String::MainCopyrightNotice << std::endl;
auto productName = Runtime::IsReleaseBuild() ? Resource::String::WindowsPackageManager : Resource::String::WindowsPackageManagerPreview;
reporter.Info() << productName(Runtime::GetClientVersion()) << std::endl << Resource::String::MainCopyrightNotice << std::endl;
}

void Command::OutputHelp(Execution::Reporter& reporter, const CommandException* exception) const
Expand All @@ -63,29 +51,7 @@ namespace AppInstaller::CLI
// Error if given
if (exception)
{
auto error = reporter.Error();
error << exception->Message();

if (!exception->Params().empty())
{
error << " :"_liv;
bool first = true;
for (const auto& param : exception->Params())
{
if (first)
{
first = false;
}
else
{
error << ',';
}
error << " '"_liv << param << '\'';
}
}

error << std::endl <<
std::endl;
reporter.Error() << exception->Message() << std::endl << std::endl;
}

// Description
Expand Down Expand Up @@ -115,7 +81,7 @@ namespace AppInstaller::CLI
}

// Output the command preamble and command chain
infoOut << Resource::String::Usage << ": winget"_liv << Utility::LocIndView{ commandChain };
infoOut << Resource::String::Usage("winget"_liv, Utility::LocIndView{ commandChain });

auto commandAliases = Aliases();
auto commands = GetVisibleCommands();
Expand Down Expand Up @@ -280,10 +246,10 @@ namespace AppInstaller::CLI
}

// Finally, the link to the documentation pages
std::string helpLink = HelpLink();
auto helpLink = Utility::LocIndString{ HelpLink() };
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
if (!helpLink.empty())
{
infoOut << std::endl << Resource::String::HelpLinkPreamble << ' ' << helpLink << std::endl;
infoOut << std::endl << Resource::String::HelpLinkPreamble(helpLink) << std::endl;
}
}

Expand Down Expand Up @@ -314,7 +280,7 @@ namespace AppInstaller::CLI
{
auto feature = ExperimentalFeature::GetFeature(command->Feature());
AICLI_LOG(CLI, Error, << "Trying to use command: " << *itr << " without enabling feature " << feature.JsonName());
throw CommandException(Resource::String::FeatureDisabledMessage, feature.JsonName());
throw CommandException(Resource::String::FeatureDisabledMessage(feature.JsonName()));
}

if (!Settings::GroupPolicies().IsEnabled(command->GroupPolicy()))
Expand All @@ -331,7 +297,7 @@ namespace AppInstaller::CLI
}

// TODO: If we get to a large number of commands, do a fuzzy search much like git
throw CommandException(Resource::String::UnrecognizedCommand, *itr);
throw CommandException(Resource::String::UnrecognizedCommand(Utility::LocIndView{ *itr }));
}

// The argument parsing state machine.
Expand Down Expand Up @@ -367,14 +333,14 @@ namespace AppInstaller::CLI
const std::optional<Execution::Args::Type>& Type() const { return m_type; }

// The actual argument string associated with Type.
const std::string& Arg() const { return m_arg; }
const Utility::LocIndString& Arg() const { return m_arg; }

// If set, indicates that the last argument produced an error.
const std::optional<CommandException>& Exception() const { return m_exception; }

private:
std::optional<Execution::Args::Type> m_type;
std::string m_arg;
Utility::LocIndString m_arg;
std::optional<CommandException> m_exception;
};

Expand Down Expand Up @@ -432,7 +398,7 @@ namespace AppInstaller::CLI
// If the next argument was to be a value, but none was provided, convert it to an exception.
else if (m_state.Type() && m_invocationItr == m_invocation.end())
{
throw CommandException(Resource::String::MissingArgumentError, m_state.Arg());
throw CommandException(Resource::String::MissingArgumentError(m_state.Arg()));
}
}

Expand Down Expand Up @@ -472,7 +438,7 @@ namespace AppInstaller::CLI
// 4. If the argument is only a double --, all further arguments are only considered as positional.
ParseArgumentsStateMachine::State ParseArgumentsStateMachine::StepInternal()
{
std::string_view currArg = *m_invocationItr;
auto currArg = Utility::LocIndView{ *m_invocationItr };
++m_invocationItr;

// If the previous step indicated a value was needed, set it and forget it.
Expand All @@ -488,15 +454,15 @@ namespace AppInstaller::CLI
const CLI::Argument* nextPositional = NextPositional();
if (!nextPositional)
{
return CommandException(Resource::String::ExtraPositionalError, currArg);
return CommandException(Resource::String::ExtraPositionalError(currArg));
}

m_executionArgs.AddArg(nextPositional->ExecArgType(), currArg);
}
// The currentArg must not be empty, and starts with a -
else if (currArg.length() == 1)
{
return CommandException(Resource::String::InvalidArgumentSpecifierError, currArg);
return CommandException(Resource::String::InvalidArgumentSpecifierError(currArg));
}
// Now it must be at least 2 chars
else if (currArg[1] != APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR)
Expand All @@ -507,7 +473,7 @@ namespace AppInstaller::CLI
auto itr = std::find_if(m_arguments.begin(), m_arguments.end(), [&](const Argument& arg) { return (currChar == arg.Alias()); });
if (itr == m_arguments.end())
{
return CommandException(Resource::String::InvalidAliasError, currArg);
return CommandException(Resource::String::InvalidAliasError(currArg));
}

if (itr->Type() == ArgumentType::Flag)
Expand All @@ -521,11 +487,11 @@ namespace AppInstaller::CLI
auto itr2 = std::find_if(m_arguments.begin(), m_arguments.end(), [&](const Argument& arg) { return (currChar == arg.Alias()); });
if (itr2 == m_arguments.end())
{
return CommandException(Resource::String::AdjoinedNotFoundError, currArg);
return CommandException(Resource::String::AdjoinedNotFoundError(currArg));
}
else if (itr2->Type() != ArgumentType::Flag)
{
return CommandException(Resource::String::AdjoinedNotFlagError, currArg);
return CommandException(Resource::String::AdjoinedNotFlagError(currArg));
}
else
{
Expand All @@ -541,7 +507,7 @@ namespace AppInstaller::CLI
}
else
{
return CommandException(Resource::String::SingleCharAfterDashError, currArg);
return CommandException(Resource::String::SingleCharAfterDashError(currArg));
}
}
else
Expand Down Expand Up @@ -584,7 +550,7 @@ namespace AppInstaller::CLI
{
if (hasValue)
{
return CommandException(Resource::String::FlagContainAdjoinedError, currArg);
return CommandException(Resource::String::FlagContainAdjoinedError(currArg));
}

m_executionArgs.AddArg(arg.ExecArgType());
Expand All @@ -604,7 +570,7 @@ namespace AppInstaller::CLI

if (!argFound)
{
return CommandException(Resource::String::InvalidNameError, currArg);
return CommandException(Resource::String::InvalidNameError(currArg));
}
}

Expand Down Expand Up @@ -661,36 +627,36 @@ namespace AppInstaller::CLI
{
auto setting = Settings::AdminSettingToString(arg.AdminSetting());
AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " disabled by admin setting " << setting);
throw CommandException(Resource::String::FeatureDisabledByAdminSettingMessage, Utility::LocIndView{ setting }, {});
throw CommandException(Resource::String::FeatureDisabledByAdminSettingMessage(setting));
}

if (!ExperimentalFeature::IsEnabled(arg.Feature()) && execArgs.Contains(arg.ExecArgType()))
{
auto feature = ExperimentalFeature::GetFeature(arg.Feature());
AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " without enabling feature " << feature.JsonName());
throw CommandException(Resource::String::FeatureDisabledMessage, feature.JsonName());
throw CommandException(Resource::String::FeatureDisabledMessage(feature.JsonName()));
}

if (arg.Required() && !execArgs.Contains(arg.ExecArgType()))
{
throw CommandException(Resource::String::RequiredArgError, arg.Name());
throw CommandException(Resource::String::RequiredArgError(arg.Name()));
}

if (arg.Limit() < execArgs.GetCount(arg.ExecArgType()))
{
throw CommandException(Resource::String::TooManyArgError, arg.Name());
throw CommandException(Resource::String::TooManyArgError(arg.Name()));
}
}

if (execArgs.Contains(Execution::Args::Type::Silent) && execArgs.Contains(Execution::Args::Type::Interactive))
{
throw CommandException(Resource::String::TooManyBehaviorsError, s_Command_ArgName_SilentAndInteractive);
throw CommandException(Resource::String::TooManyBehaviorsError(s_Command_ArgName_SilentAndInteractive));
}

if (execArgs.Contains(Execution::Args::Type::CustomHeader) && !execArgs.Contains(Execution::Args::Type::Source) &&
!execArgs.Contains(Execution::Args::Type::SourceName))
{
throw CommandException(Resource::String::HeaderArgumentNotApplicableWithoutSource, Argument::ForType(Execution::Args::Type::CustomHeader).Name());
throw CommandException(Resource::String::HeaderArgumentNotApplicableWithoutSource(Argument::ForType(Execution::Args::Type::CustomHeader).Name()));
}

if (execArgs.Contains(Execution::Args::Type::Count))
Expand Down Expand Up @@ -719,15 +685,17 @@ namespace AppInstaller::CLI
{
applicableArchitectures.emplace_back(Utility::ToString(i));
}
throw CommandException(Resource::String::InvalidArgumentValueError, Argument::ForType(Execution::Args::Type::InstallArchitecture).Name(), std::forward<std::vector<Utility::LocIndString>>((applicableArchitectures)));

auto validOptions = Utility::Join(", "_liv, applicableArchitectures);
throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::InstallArchitecture).Name(), validOptions));
}
}

if (execArgs.Contains(Execution::Args::Type::Locale))
{
if (!Locale::IsWellFormedBcp47Tag(execArgs.GetArg(Execution::Args::Type::Locale)))
{
throw CommandException(Resource::String::InvalidArgumentValueErrorWithoutValidValues, Argument::ForType(Execution::Args::Type::Locale).Name(), {});
throw CommandException(Resource::String::InvalidArgumentValueErrorWithoutValidValues(Argument::ForType(Execution::Args::Type::Locale).Name()));
}
}

Expand Down
17 changes: 1 addition & 16 deletions src/AppInstallerCLICore/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,10 @@ namespace AppInstaller::CLI
struct CommandException
{
CommandException(Resource::LocString message) : m_message(std::move(message)) {}

// The message should be a localized string.
// The parameters can be either localized or not.
// We 'convert' the param to a localization independent view here if needed.
CommandException(Resource::LocString message, Resource::LocString param) : m_message(std::move(message)), m_params({ param }) {}
CommandException(Resource::LocString message, std::string_view param) : m_message(std::move(message)), m_params({ Utility::LocIndString{ param } }) {}

// The message should be a localized string, but the replacement and parameters are not.
// This supports replacing %1 in the message with the replace value.
CommandException(Resource::LocString message, Utility::LocIndView replace, std::vector<Utility::LocIndString>&& params) :
m_message(std::move(message)), m_replace(replace), m_params(std::move(params)) {}

const Utility::LocIndString Message() const;
const std::vector<Utility::LocIndString>& Params() const { return m_params; }
const Utility::LocIndString Message() const { return m_message; }

private:
Resource::LocString m_message;
std::optional<Utility::LocIndString> m_replace;
std::vector<Utility::LocIndString> m_params;
};

// Flags to control the behavior of the command output.
Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/FeaturesCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ namespace AppInstaller::CLI
{
table.OutputLine({
std::string{ feature.Name() },
Resource::Loader::Instance().ResolveString(ExperimentalFeature::IsEnabled(feature.GetFeature()) ? Resource::String::FeaturesEnabled : Resource::String::FeaturesDisabled),
Resource::LocString{ ExperimentalFeature::IsEnabled(feature.GetFeature()) ? Resource::String::FeaturesEnabled : Resource::String::FeaturesDisabled},
std::string { feature.JsonName() },
std::string{ feature.Link() } });
}
Expand Down
3 changes: 2 additions & 1 deletion src/AppInstallerCLICore/Commands/InstallCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ namespace AppInstaller::CLI
{
if (ConvertToScopeEnum(execArgs.GetArg(Args::Type::InstallScope)) == Manifest::ScopeEnum::Unknown)
{
throw CommandException(Resource::String::InvalidArgumentValueError, s_ArgumentName_Scope, { "user"_lis, "machine"_lis });
auto validOptions = Utility::Join(", "_liv, std::vector<Utility::LocIndString>{ "user"_lis, "machine"_lis});
throw CommandException(Resource::String::InvalidArgumentValueError(s_ArgumentName_Scope, validOptions));
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/AppInstallerCLICore/Commands/RootCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,15 @@ namespace AppInstaller::CLI
info << std::endl <<
"Windows: "_liv << Runtime::GetOSVersion() << std::endl;

info << Resource::String::SystemArchitecture << ": "_liv << Utility::ToString(Utility::GetSystemArchitecture()) << std::endl;
info << Resource::String::SystemArchitecture(Utility::ToString(Utility::GetSystemArchitecture())) << std::endl;

if (Runtime::IsRunningInPackagedContext())
{
info << Resource::String::Package << ": "_liv << Runtime::GetPackageVersion() << std::endl;
info << Resource::String::Package(Runtime::GetPackageVersion()) << std::endl;
};

info << std::endl << Resource::String::Logs << ": "_liv << Runtime::GetPathTo(Runtime::PathName::DefaultLogLocationForDisplay).u8string() << std::endl;
info << std::endl << Resource::String::UserSettings << ": "_liv << UserSettings::SettingsFilePath(true).u8string() << std::endl;
info << std::endl << Resource::String::Logs(Utility::LocIndView{ Runtime::GetPathTo(Runtime::PathName::DefaultLogLocationForDisplay).u8string() }) << std::endl;
info << std::endl << Resource::String::UserSettings(Utility::LocIndView{ UserSettings::SettingsFilePath(true).u8string() }) << std::endl;

info << std::endl;

Expand Down
Loading