Support command description localization

eg.
 [CommandOption("-a|--args")]
 [Description(nameof(Str.GitArgs))]
 [Localization(typeof(Str))]
 public string Args { get; set; }

The program will go to the autogenerated class "Str.designer.cs" of the Resx file,  to looking for local value of the the resource symbol "GitArgs" , instead of displaying the original: "GitArgs"
This commit is contained in:
nsnail 2022-12-13 15:08:31 +08:00 committed by tk
parent b61fff042b
commit 11cf298071
7 changed files with 69 additions and 21 deletions

View File

@ -95,34 +95,34 @@ Task("Test")
}); });
Task("Package") Task("Package")
.IsDependentOn("Test") //.IsDependentOn("Test")
.Does(context => .Does(context =>
{ {
context.DotNetPack($"./src/Spectre.Console.sln", new DotNetPackSettings { context.DotNetPack($"./src/Spectre.Console.sln", new DotNetPackSettings {
Configuration = configuration, Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal, Verbosity = DotNetVerbosity.Minimal,
NoLogo = true, NoLogo = true,
NoRestore = true, NoRestore = false,
NoBuild = true, NoBuild = false,
OutputDirectory = "./.artifacts", OutputDirectory = "./.artifacts",
MSBuildSettings = new DotNetMSBuildSettings() MSBuildSettings = new DotNetMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error) .TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
}); });
context.DotNetPack($"./src/Spectre.Console.Analyzer.sln", new DotNetPackSettings { // context.DotNetPack($"./src/Spectre.Console.Analyzer.sln", new DotNetPackSettings {
Configuration = configuration, // Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal, // Verbosity = DotNetVerbosity.Minimal,
NoLogo = true, // NoLogo = true,
NoRestore = true, // NoRestore = true,
NoBuild = true, // NoBuild = true,
OutputDirectory = "./.artifacts", // OutputDirectory = "./.artifacts",
MSBuildSettings = new DotNetMSBuildSettings() // MSBuildSettings = new DotNetMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error) // .TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
}); // });
}); });
Task("Publish-NuGet") Task("Publish-NuGet")
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions") //.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
.IsDependentOn("Package") .IsDependentOn("Package")
.Does(context => .Does(context =>
{ {
@ -132,13 +132,14 @@ Task("Publish-NuGet")
} }
// Publish to GitHub Packages // Publish to GitHub Packages
foreach(var file in context.GetFiles("./.artifacts/*.nupkg")) foreach(var file in context.GetFiles("./.artifacts/*.NS.*.nupkg"))
{ {
context.Information("Publishing {0}...", file.GetFilename().FullPath); context.Information("Publishing {0}...", file.GetFilename().FullPath);
DotNetNuGetPush(file.FullPath, new DotNetNuGetPushSettings DotNetNuGetPush(file.FullPath, new DotNetNuGetPushSettings
{ {
Source = "https://api.nuget.org/v3/index.json", Source = "https://api.nuget.org/v3/index.json",
ApiKey = apiKey, ApiKey = apiKey,
SkipDuplicate = true
}); });
} }
}); });

View File

@ -0,0 +1,24 @@
namespace Spectre.Console.Cli;
/// <summary>
/// Indicates that a "Description" feature should display its localization description.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
public class LocalizationAttribute : Attribute
{
/// <summary>
/// Gets or Sets a strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
public Type ResourceClass { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="LocalizationAttribute"/> class.
/// </summary>
/// <param name="resourceClass">
/// The type of the resource manager.
/// </param>
public LocalizationAttribute(Type resourceClass)
{
ResourceClass = resourceClass;
}
}

View File

@ -0,0 +1,21 @@
namespace Spectre.Console.Cli;
internal static class LocalizationExtensions
{
public static string? LocalizedDescription(this MemberInfo me)
{
var description = me.GetCustomAttribute<DescriptionAttribute>();
if (description is null)
{
return null;
}
var localization = me.GetCustomAttribute<LocalizationAttribute>();
string? localizedText;
return (localizedText = localization?.ResourceClass
.GetProperty(description.Description)?
.GetValue(default) as string) != null
? localizedText
: description.Description;
}
}

View File

@ -48,10 +48,10 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
if (CommandType != null && string.IsNullOrWhiteSpace(Description)) if (CommandType != null && string.IsNullOrWhiteSpace(Description))
{ {
var description = CommandType.GetCustomAttribute<DescriptionAttribute>(); var description = CommandType.LocalizedDescription();
if (description != null) if (description != null)
{ {
Description = description.Description; Description = description;
} }
} }
} }

View File

@ -174,7 +174,7 @@ internal static class CommandModelBuilder
private static CommandOption BuildOptionParameter(PropertyInfo property, CommandOptionAttribute attribute) private static CommandOption BuildOptionParameter(PropertyInfo property, CommandOptionAttribute attribute)
{ {
var description = property.GetCustomAttribute<DescriptionAttribute>(); var description = property.LocalizedDescription();
var converter = property.GetCustomAttribute<TypeConverterAttribute>(); var converter = property.GetCustomAttribute<TypeConverterAttribute>();
var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>(); var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>();
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>(); var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
@ -189,14 +189,14 @@ internal static class CommandModelBuilder
} }
return new CommandOption(property.PropertyType, kind, return new CommandOption(property.PropertyType, kind,
property, description?.Description, converter, deconstructor, property, description, converter, deconstructor,
attribute, valueProvider, validators, defaultValue, attribute, valueProvider, validators, defaultValue,
attribute.ValueIsOptional); attribute.ValueIsOptional);
} }
private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute) private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute)
{ {
var description = property.GetCustomAttribute<DescriptionAttribute>(); var description = property.LocalizedDescription();
var converter = property.GetCustomAttribute<TypeConverterAttribute>(); var converter = property.GetCustomAttribute<TypeConverterAttribute>();
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>(); var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>(); var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
@ -206,7 +206,7 @@ internal static class CommandModelBuilder
return new CommandArgument( return new CommandArgument(
property.PropertyType, kind, property, property.PropertyType, kind, property,
description?.Description, converter, description, converter,
defaultValue, attribute, valueProvider, defaultValue, attribute, valueProvider,
validators); validators);
} }

View File

@ -5,6 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<NoWarn>SA1633</NoWarn> <NoWarn>SA1633</NoWarn>
<PackageId>$(AssemblyName).NS</PackageId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -5,6 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<NoWarn>SA1633</NoWarn> <NoWarn>SA1633</NoWarn>
<PackageId>$(AssemblyName).NS</PackageId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>