Add localization support to help provider (#1349)

Closes #1349
This commit is contained in:
Frank Ray 2023-11-10 23:22:52 +00:00 committed by GitHub
parent 023c77ff09
commit 250b1f4c9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1057 additions and 36 deletions

View File

@ -39,7 +39,30 @@ public static class ConfiguratorExtensions
configurator.SetHelpProvider<T>();
return configurator;
}
}
/// <summary>
/// Sets the culture for the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="culture">The culture.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
/// <remarks>
/// Text displayed by <see cref="Help.HelpProvider"/> can be localised, but defaults to English.
/// Setting the application culture informs the resource manager which culture to use when fetching strings.
/// English will be used when a culture has not been specified
/// or a string has not been localised for the specified culture.
/// </remarks>
public static IConfigurator SetApplicationCulture(this IConfigurator configurator, CultureInfo? culture)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.Culture = culture;
return configurator;
}
/// <summary>
/// Sets the name of the application.

View File

@ -1,3 +1,5 @@
using Spectre.Console.Cli.Resources;
namespace Spectre.Console.Cli.Help;
/// <summary>
@ -7,7 +9,9 @@ namespace Spectre.Console.Cli.Help;
/// Other IHelpProvider implementations can be injected into the CommandApp, if desired.
/// </remarks>
public class HelpProvider : IHelpProvider
{
{
private HelpProviderResources resources;
/// <summary>
/// Gets a value indicating how many examples from direct children to show in the help text.
/// </summary>
@ -67,17 +71,17 @@ public class HelpProvider : IHelpProvider
DefaultValue = defaultValue;
}
public static IReadOnlyList<HelpOption> Get(ICommandInfo? command)
public static IReadOnlyList<HelpOption> Get(ICommandInfo? command, HelpProviderResources resources)
{
var parameters = new List<HelpOption>();
parameters.Add(new HelpOption("h", "help", null, null, "Prints help information", null));
parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null));
// Version information applies to the entire application
// Include the "-v" option in the help when at the root of the command line application
// Don't allow the "-v" option if users have specified one or more sub-commands
if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false))
{
parameters.Add(new HelpOption("v", "version", null, null, "Prints version information", null));
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
}
parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
@ -98,7 +102,9 @@ public class HelpProvider : IHelpProvider
{
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
resources = new HelpProviderResources(settings.Culture);
}
/// <inheritdoc/>
@ -143,7 +149,7 @@ public class HelpProvider : IHelpProvider
}
var composer = new Composer();
composer.Style("yellow", "DESCRIPTION:").LineBreak();
composer.Style("yellow", $"{resources.Description}:").LineBreak();
composer.Text(command.Description).LineBreak();
yield return composer.LineBreak();
}
@ -157,15 +163,15 @@ public class HelpProvider : IHelpProvider
public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandInfo? command)
{
var composer = new Composer();
composer.Style("yellow", "USAGE:").LineBreak();
composer.Style("yellow", $"{resources.Usage}:").LineBreak();
composer.Tab().Text(model.ApplicationName);
var parameters = new List<string>();
if (command == null)
{
parameters.Add("[grey][[OPTIONS]][/]");
parameters.Add("[aqua]<COMMAND>[/]");
parameters.Add($"[grey][[{resources.Options}]][/]");
parameters.Add($"[aqua]<{resources.Command}>[/]");
}
else
{
@ -208,20 +214,20 @@ public class HelpProvider : IHelpProvider
if (isCurrent)
{
parameters.Add("[grey][[OPTIONS]][/]");
parameters.Add($"[grey][[{resources.Options}]][/]");
}
}
if (command.IsBranch && command.DefaultCommand == null)
{
// The user must specify the command
parameters.Add("[aqua]<COMMAND>[/]");
parameters.Add($"[aqua]<{resources.Command}>[/]");
}
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
{
// We are on a branch with a default command
// The user can optionally specify the command
parameters.Add("[aqua][[COMMAND]][/]");
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
else if (command.IsDefaultCommand)
{
@ -231,7 +237,7 @@ public class HelpProvider : IHelpProvider
{
// Commands other than the default are present
// So make these optional in the usage statement
parameters.Add("[aqua][[COMMAND]][/]");
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
}
}
@ -298,7 +304,7 @@ public class HelpProvider : IHelpProvider
{
var composer = new Composer();
composer.LineBreak();
composer.Style("yellow", "EXAMPLES:").LineBreak();
composer.Style("yellow", $"{resources.Examples}:").LineBreak();
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
{
@ -330,7 +336,7 @@ public class HelpProvider : IHelpProvider
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]ARGUMENTS:[/]"),
new Markup($"[yellow]{resources.Arguments}:[/]"),
new Markup(Environment.NewLine),
};
@ -366,7 +372,7 @@ public class HelpProvider : IHelpProvider
public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command)
{
// Collect all options into a single structure.
var parameters = HelpOption.Get(command);
var parameters = HelpOption.Get(command, resources);
if (parameters.Count == 0)
{
return Array.Empty<IRenderable>();
@ -375,7 +381,7 @@ public class HelpProvider : IHelpProvider
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]OPTIONS:[/]"),
new Markup($"[yellow]{resources.Options}:[/]"),
new Markup(Environment.NewLine),
};
@ -434,7 +440,7 @@ public class HelpProvider : IHelpProvider
if (defaultValueColumn)
{
grid.AddRow(" ", "[lime]DEFAULT[/]", " ");
grid.AddRow(" ", $"[lime]{resources.Default}[/]", " ");
}
foreach (var option in helpOptions)
@ -487,7 +493,7 @@ public class HelpProvider : IHelpProvider
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]COMMANDS:[/]"),
new Markup($"[yellow]{resources.Commands}:[/]"),
new Markup(Environment.NewLine),
};

View File

@ -0,0 +1,131 @@
using System.Resources;
namespace Spectre.Console.Cli.Help;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
internal class HelpProviderResources
{
private readonly ResourceManager resourceManager = new ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
private readonly CultureInfo? resourceCulture = null;
public HelpProviderResources()
{
}
public HelpProviderResources(CultureInfo? culture)
{
resourceCulture = culture;
}
/// <summary>
/// Gets the localised string for ARGUMENTS.
/// </summary>
internal string Arguments
{
get
{
return resourceManager.GetString("Arguments", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for COMMAND.
/// </summary>
internal string Command
{
get
{
return resourceManager.GetString("Command", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for COMMANDS.
/// </summary>
internal string Commands
{
get
{
return resourceManager.GetString("Commands", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for DEFAULT.
/// </summary>
internal string Default
{
get
{
return resourceManager.GetString("Default", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for DESCRIPTION.
/// </summary>
internal string Description
{
get
{
return resourceManager.GetString("Description", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for EXAMPLES.
/// </summary>
internal string Examples
{
get
{
return resourceManager.GetString("Examples", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for OPTIONS.
/// </summary>
internal string Options
{
get
{
return resourceManager.GetString("Options", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for Prints help information.
/// </summary>
internal string PrintHelpDescription
{
get
{
return resourceManager.GetString("PrintHelpDescription", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for Prints version information.
/// </summary>
internal string PrintVersionDescription
{
get
{
return resourceManager.GetString("PrintVersionDescription", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for USAGE.
/// </summary>
internal string Usage
{
get
{
return resourceManager.GetString("Usage", resourceCulture) ?? string.Empty;
}
}
}

View File

@ -5,6 +5,17 @@ namespace Spectre.Console.Cli;
/// </summary>
public interface ICommandAppSettings
{
/// <summary>
/// Gets or sets the culture.
/// </summary>
/// <remarks>
/// Text displayed by <see cref="Help.HelpProvider"/> can be localised, but defaults to English.
/// Setting this property informs the resource manager which culture to use when fetching strings.
/// English will be used when a culture has not been specified (ie. this property is null)
/// or a string has not been localised for the specified culture.
/// </remarks>
CultureInfo? Culture { get; set; }
/// <summary>
/// Gets or sets the application name.
/// </summary>

View File

@ -2,6 +2,7 @@ namespace Spectre.Console.Cli;
internal sealed class CommandAppSettings : ICommandAppSettings
{
public CultureInfo? Culture { get; set; }
public string? ApplicationName { get; set; }
public string? ApplicationVersion { get; set; }
public int MaximumIndirectExamples { get; set; }
@ -19,8 +20,8 @@ internal sealed class CommandAppSettings : ICommandAppSettings
public ParsingMode ParsingMode =>
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
public Func<Exception, int>? ExceptionHandler { get; set; }
public Func<Exception, int>? ExceptionHandler { get; set; }
public CommandAppSettings(ITypeRegistrar registrar)
{
Registrar = new TypeRegistrar(registrar);

View File

@ -0,0 +1,153 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Spectre.Console.Cli.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class HelpProvider {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal HelpProvider() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to ARGUMENTS.
/// </summary>
internal static string Arguments {
get {
return ResourceManager.GetString("Arguments", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to COMMAND.
/// </summary>
internal static string Command {
get {
return ResourceManager.GetString("Command", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to COMMANDS.
/// </summary>
internal static string Commands {
get {
return ResourceManager.GetString("Commands", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to DEFAULT.
/// </summary>
internal static string Default {
get {
return ResourceManager.GetString("Default", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to DESCRIPTION.
/// </summary>
internal static string Description {
get {
return ResourceManager.GetString("Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to EXAMPLES.
/// </summary>
internal static string Examples {
get {
return ResourceManager.GetString("Examples", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OPTIONS.
/// </summary>
internal static string Options {
get {
return ResourceManager.GetString("Options", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prints help information.
/// </summary>
internal static string PrintHelpDescription {
get {
return ResourceManager.GetString("PrintHelpDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prints version information.
/// </summary>
internal static string PrintVersionDescription {
get {
return ResourceManager.GetString("PrintVersionDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to USAGE.
/// </summary>
internal static string Usage {
get {
return ResourceManager.GetString("Usage", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>ARGUMENTE</value>
</data>
<data name="Command" xml:space="preserve">
<value>KOMMANDO</value>
</data>
<data name="Commands" xml:space="preserve">
<value>KOMMANDOS</value>
</data>
<data name="Default" xml:space="preserve">
<value>STANDARDWERT</value>
</data>
<data name="Description" xml:space="preserve">
<value>BESCHREIBUNG</value>
</data>
<data name="Examples" xml:space="preserve">
<value>BEISPIELE</value>
</data>
<data name="Options" xml:space="preserve">
<value>OPTIONEN</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Zeigt Hilfe an</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Zeigt Versionsinformationen an</value>
</data>
<data name="Usage" xml:space="preserve">
<value>VERWENDUNG</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>ARGUMENTS</value>
</data>
<data name="Command" xml:space="preserve">
<value>COMMANDE</value>
</data>
<data name="Commands" xml:space="preserve">
<value>COMMANDES</value>
</data>
<data name="Default" xml:space="preserve">
<value>DÉFAUT</value>
</data>
<data name="Description" xml:space="preserve">
<value>DESCRIPTION</value>
</data>
<data name="Examples" xml:space="preserve">
<value>EXEMPLES</value>
</data>
<data name="Options" xml:space="preserve">
<value>OPTIONS</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Affiche l'aide</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Affiche la version</value>
</data>
<data name="Usage" xml:space="preserve">
<value>UTILISATION</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>ARGUMENTS</value>
</data>
<data name="Command" xml:space="preserve">
<value>COMMAND</value>
</data>
<data name="Commands" xml:space="preserve">
<value>COMMANDS</value>
</data>
<data name="Default" xml:space="preserve">
<value>DEFAULT</value>
</data>
<data name="Description" xml:space="preserve">
<value>DESCRIPTION</value>
</data>
<data name="Examples" xml:space="preserve">
<value>EXAMPLES</value>
</data>
<data name="Options" xml:space="preserve">
<value>OPTIONS</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Prints help information</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Prints version information</value>
</data>
<data name="Usage" xml:space="preserve">
<value>USAGE</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>ARGUMENT</value>
</data>
<data name="Command" xml:space="preserve">
<value>KOMMANDO</value>
</data>
<data name="Commands" xml:space="preserve">
<value>KOMMANDON</value>
</data>
<data name="Default" xml:space="preserve">
<value>STANDARD</value>
</data>
<data name="Description" xml:space="preserve">
<value>BESKRIVNING</value>
</data>
<data name="Examples" xml:space="preserve">
<value>EXEMPEL</value>
</data>
<data name="Options" xml:space="preserve">
<value>VAL</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Skriver ut hjälpinformation</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Skriver ut versionsnummer</value>
</data>
<data name="Usage" xml:space="preserve">
<value>ANVÄNDING</value>
</data>
</root>

View File

@ -31,4 +31,19 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\HelpProvider.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>HelpProvider.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\HelpProvider.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>HelpProvider.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
BESCHREIBUNG:
The lion command.
VERWENDUNG:
myapp <TEETH> [LEGS] [OPTIONEN] [KOMMANDO]
BEISPIELE:
myapp 20 --alive
ARGUMENTE:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
OPTIONEN:
STANDARDWERT
-h, --help Zeigt Hilfe an
-v, --version Zeigt Versionsinformationen an
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
KOMMANDOS:
giraffe <LENGTH> The giraffe command

View File

@ -4,6 +4,9 @@ The lion command.
USAGE:
myapp <TEETH> [LEGS] [OPTIONS] [COMMAND]
EXAMPLES:
myapp 20 --alive
ARGUMENTS:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs

View File

@ -0,0 +1,25 @@
DESCRIPTION:
The lion command.
UTILISATION:
myapp <TEETH> [LEGS] [OPTIONS] [COMMANDE]
EXEMPLES:
myapp 20 --alive
ARGUMENTS:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
OPTIONS:
DÉFAUT
-h, --help Affiche l'aide
-v, --version Affiche la version
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
COMMANDES:
giraffe <LENGTH> The giraffe command

View File

@ -0,0 +1,25 @@
BESKRIVNING:
The lion command.
ANVÄNDING:
myapp <TEETH> [LEGS] [VAL] [KOMMANDO]
EXEMPEL:
myapp 20 --alive
ARGUMENT:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
VAL:
STANDARD
-h, --help Skriver ut hjälpinformation
-v, --version Skriver ut versionsnummer
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
KOMMANDON:
giraffe <LENGTH> The giraffe command

View File

@ -29,15 +29,4 @@
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Expectations\Help\Default_Without_Args_Additional.Output.verified.txt">
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
<DependentUpon>%(ParentFile).cs</DependentUpon>
</None>
<None Update="Expectations\Help\Default_Greeter.Output.verified.txt">
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
<DependentUpon>%(ParentFile).cs</DependentUpon>
</None>
</ItemGroup>
</Project>

View File

@ -230,15 +230,27 @@ public sealed partial class CommandAppTests
return Verifier.Verify(result.Output);
}
[Fact]
[Theory]
[InlineData(null, "EN")]
[InlineData("", "EN")]
[InlineData("en", "EN")]
[InlineData("en-EN", "EN")]
[InlineData("fr", "FR")]
[InlineData("fr-FR", "FR")]
[InlineData("sv", "SV")]
[InlineData("sv-SE", "SV")]
[InlineData("de", "DE")]
[InlineData("de-DE", "DE")]
[Expectation("Default_Without_Args_Additional")]
public Task Should_Output_Default_Command_And_Additional_Commands_When_Default_Command_Has_Required_Parameters_And_Is_Called_Without_Args()
public Task Should_Output_Default_Command_And_Additional_Commands_When_Default_Command_Has_Required_Parameters_And_Is_Called_Without_Args_Localised(string culture, string expectationPrefix)
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<LionCommand>();
fixture.Configure(configurator =>
{
configurator.AddExample("20", "--alive");
configurator.SetApplicationCulture(string.IsNullOrEmpty(culture) ? null : new CultureInfo(culture));
configurator.SetApplicationName("myapp");
configurator.AddCommand<GiraffeCommand>("giraffe");
});
@ -247,7 +259,9 @@ public sealed partial class CommandAppTests
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
var settings = new VerifySettings();
settings.DisableRequireUniquePrefix();
return Verifier.Verify(result.Output, settings).UseTextForParameters(expectationPrefix);
}
[Fact]