Skip to content

Commit

Permalink
Add localization support to help provider (#1349)
Browse files Browse the repository at this point in the history
Closes #1349
  • Loading branch information
FrankRay78 authored Nov 10, 2023
1 parent 023c77f commit 250b1f4
Show file tree
Hide file tree
Showing 17 changed files with 1,057 additions and 36 deletions.
25 changes: 24 additions & 1 deletion src/Spectre.Console.Cli/ConfiguratorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,30 @@ public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)

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.
Expand Down
44 changes: 25 additions & 19 deletions src/Spectre.Console.Cli/Help/HelpProvider.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Spectre.Console.Cli.Resources;

namespace Spectre.Console.Cli.Help;

/// <summary>
Expand All @@ -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>
Expand Down Expand Up @@ -67,17 +71,17 @@ public HelpOption(string? @short, string? @long, string? @value, bool? valueIsOp
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 =>
Expand All @@ -98,7 +102,9 @@ public HelpProvider(ICommandAppSettings settings)
{
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;

resources = new HelpProviderResources(settings.Culture);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -143,7 +149,7 @@ public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICom
}

var composer = new Composer();
composer.Style("yellow", "DESCRIPTION:").LineBreak();
composer.Style("yellow", $"{resources.Description}:").LineBreak();
composer.Text(command.Description).LineBreak();
yield return composer.LineBreak();
}
Expand All @@ -157,15 +163,15 @@ public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICom
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
{
Expand Down Expand Up @@ -208,20 +214,20 @@ public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandIn

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)
{
Expand All @@ -231,7 +237,7 @@ public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandIn
{
// Commands other than the default are present
// So make these optional in the usage statement
parameters.Add("[aqua][[COMMAND]][/]");
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
}
}
Expand Down Expand Up @@ -298,7 +304,7 @@ public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, IComman
{
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++)
{
Expand Down Expand Up @@ -330,7 +336,7 @@ public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, IComma
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]ARGUMENTS:[/]"),
new Markup($"[yellow]{resources.Arguments}:[/]"),
new Markup(Environment.NewLine),
};

Expand Down Expand Up @@ -366,7 +372,7 @@ public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, IComma
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>();
Expand All @@ -375,7 +381,7 @@ public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommand
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]OPTIONS:[/]"),
new Markup($"[yellow]{resources.Options}:[/]"),
new Markup(Environment.NewLine),
};

Expand Down Expand Up @@ -434,7 +440,7 @@ static string GetOptionParts(HelpOption option)

if (defaultValueColumn)
{
grid.AddRow(" ", "[lime]DEFAULT[/]", " ");
grid.AddRow(" ", $"[lime]{resources.Default}[/]", " ");
}

foreach (var option in helpOptions)
Expand Down Expand Up @@ -487,7 +493,7 @@ public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, IComman
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup("[yellow]COMMANDS:[/]"),
new Markup($"[yellow]{resources.Commands}:[/]"),
new Markup(Environment.NewLine),
};

Expand Down
131 changes: 131 additions & 0 deletions src/Spectre.Console.Cli/Help/HelpProviderResources.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
11 changes: 11 additions & 0 deletions src/Spectre.Console.Cli/ICommandAppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 250b1f4

Please sign in to comment.