From b777fd3123dc0eb04b5002985198abdaf17d5dd0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 15:15:12 +0800 Subject: [PATCH 01/23] Support enum localization --- .../Attributes/EnumLocalizeAttribute.cs | 12 + .../Attributes/EnumLocalizeKeyAttribute.cs | 33 +++ .../Attributes/EnumLocalizeValueAttribute.cs | 33 +++ .../Constants.cs | 7 +- Flow.Launcher.Localization.Shared/Helper.cs | 26 +- .../AnalyzerReleases.Unshipped.md | 1 + .../Localize/EnumSourceGenerator.cs | 269 ++++++++++++++++++ .../Localize/LocalizeSourceGenerator.cs | 16 +- .../SourceGeneratorDiagnostics.cs | 9 + 9 files changed, 387 insertions(+), 19 deletions(-) create mode 100644 Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs create mode 100644 Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs create mode 100644 Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs create mode 100644 Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs new file mode 100644 index 0000000..8c98b89 --- /dev/null +++ b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Flow.Launcher.Localization.SourceGenerators.Shared.Attributes +{ + /// + /// Attribute to mark an enum for localization. + /// + [AttributeUsage(AttributeTargets.Enum)] + public class EnumLocalizeAttribute : Attribute + { + } +} diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs new file mode 100644 index 0000000..de4a08f --- /dev/null +++ b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs @@ -0,0 +1,33 @@ +using System; + +namespace Flow.Launcher.Localization.SourceGenerators.Shared.Attributes +{ + /// + /// Attribute to mark a localization key for an enum field. + /// + [AttributeUsage(AttributeTargets.Field)] + public class EnumLocalizeKeyAttribute : Attribute + { + public static readonly EnumLocalizeKeyAttribute Default = new EnumLocalizeKeyAttribute(); + + public EnumLocalizeKeyAttribute() : this(string.Empty) + { + } + + public EnumLocalizeKeyAttribute(string enumLocalizeKey) + { + EnumLocalizeKey = enumLocalizeKey; + } + + public virtual string LocalizeKey => EnumLocalizeKey; + + protected string EnumLocalizeKey { get; set; } + + public override bool Equals(object obj) => + obj is EnumLocalizeKeyAttribute other && other.LocalizeKey == LocalizeKey; + + public override int GetHashCode() => LocalizeKey?.GetHashCode() ?? 0; + + public override bool IsDefaultAttribute() => Equals(Default); + } +} diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs new file mode 100644 index 0000000..5af0499 --- /dev/null +++ b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs @@ -0,0 +1,33 @@ +using System; + +namespace Flow.Launcher.Localization.SourceGenerators.Shared.Attributes +{ + /// + /// Attribute to mark a localization value for an enum field. + /// + [AttributeUsage(AttributeTargets.Field)] + public class EnumLocalizeValueAttribute : Attribute + { + public static readonly EnumLocalizeValueAttribute Default = new EnumLocalizeValueAttribute(); + + public EnumLocalizeValueAttribute() : this(string.Empty) + { + } + + public EnumLocalizeValueAttribute(string enumLocalizeValue) + { + EnumLocalizeValue = enumLocalizeValue; + } + + public virtual string LocalizeValue => EnumLocalizeValue; + + protected string EnumLocalizeValue { get; set; } + + public override bool Equals(object obj) => + obj is EnumLocalizeValueAttribute other && other.LocalizeValue == LocalizeValue; + + public override int GetHashCode() => LocalizeValue?.GetHashCode() ?? 0; + + public override bool IsDefaultAttribute() => Equals(Default); + } +} diff --git a/Flow.Launcher.Localization.Shared/Constants.cs b/Flow.Launcher.Localization.Shared/Constants.cs index 568da5d..f5c9279 100644 --- a/Flow.Launcher.Localization.Shared/Constants.cs +++ b/Flow.Launcher.Localization.Shared/Constants.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using Flow.Launcher.Localization.SourceGenerators.Shared.Attributes; +using System.Text.RegularExpressions; namespace Flow.Launcher.Localization.Shared { @@ -20,6 +21,10 @@ public static class Constants public const string OldLocalizationMethodName = "GetTranslation"; public const string StringFormatMethodName = "Format"; public const string StringFormatTypeName = "string"; + public const string EnumLocalizeClassSuffix = "Data"; + public const string EnumLocalizeAttributeName = nameof(EnumLocalizeAttribute); + public const string EnumLocalizeKeyAttributeName = nameof(EnumLocalizeKeyAttribute); + public const string EnumLocalizeValueAttributeName = nameof(EnumLocalizeValueAttribute); public static readonly Regex LanguagesXamlRegex = new Regex(@"\\Languages\\[^\\]+\.xaml$", RegexOptions.IgnoreCase); public static readonly string[] OldLocalizationClasses = { "IPublicAPI", "Internationalization" }; diff --git a/Flow.Launcher.Localization.Shared/Helper.cs b/Flow.Launcher.Localization.Shared/Helper.cs index d9502ac..044fa45 100644 --- a/Flow.Launcher.Localization.Shared/Helper.cs +++ b/Flow.Launcher.Localization.Shared/Helper.cs @@ -1,9 +1,11 @@ -using Microsoft.CodeAnalysis; +using System; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using System.Linq; -using System.Threading; namespace Flow.Launcher.Localization.Shared { @@ -81,5 +83,23 @@ private static Location GetCodeFixLocation(PropertyDeclarationSyntax property, S } #endregion + + #region Tab String + + public static string Spacing(int n) + { + Span spaces = stackalloc char[n * 4]; + spaces.Fill(' '); + + var sb = new StringBuilder(n * 4); + foreach (var c in spaces) + { + _ = sb.Append(c); + } + + return sb.ToString(); + } + + #endregion } } diff --git a/Flow.Launcher.Localization.SourceGenerators/AnalyzerReleases.Unshipped.md b/Flow.Launcher.Localization.SourceGenerators/AnalyzerReleases.Unshipped.md index f60d258..c1ad7a3 100644 --- a/Flow.Launcher.Localization.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/Flow.Launcher.Localization.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -11,3 +11,4 @@ FLSG0004 | Localization | Warning | FLSG0004_ContextPropertyNotStatic FLSG0005 | Localization | Warning | FLSG0005_ContextPropertyIsPrivate FLSG0006 | Localization | Warning | FLSG0006_ContextPropertyIsProtected FLSG0007 | Localization | Warning | FLSG0007_LocalizationKeyUnused +FLSG0008 | Localization | Warning | FLSG0008_EnumFieldLocalizationKeyValueInvalid diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs new file mode 100644 index 0000000..88a73cc --- /dev/null +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Flow.Launcher.Localization.Shared; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Flow.Launcher.Localization.SourceGenerators.Localize +{ + [Generator] + public partial class EnumSourceGenerator : IIncrementalGenerator + { + #region Fields + + private static readonly Version PackageVersion = typeof(EnumSourceGenerator).Assembly.GetName().Version; + + private static readonly ImmutableArray _emptyEnumFields = ImmutableArray.Empty; + + #endregion + + #region Incremental Generator + + /// + /// Initializes the generator and registers source output based on resource files. + /// + /// The initialization context. + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var enumDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: (s, _) => s is EnumDeclarationSyntax, + transform: (ctx, _) => (EnumDeclarationSyntax)ctx.Node) + .Where(ed => ed.AttributeLists.Count > 0) + .Collect(); + + var compilation = context.CompilationProvider; + + var compilationEnums = enumDeclarations.Combine(compilation); + + context.RegisterSourceOutput(compilationEnums, Execute); + } + + /// + /// Executes the generation of string properties based on the provided data. + /// + /// The source production context. + /// The provided data. + private void Execute(SourceProductionContext spc, + (ImmutableArray Enums, Compilation Compilation) data) + { + var enums = data.Enums; + var compilation = data.Compilation; + + var assemblyName = compilation.AssemblyName ?? Constants.DefaultNamespace; + + foreach (var enumDeclaration in enums.Distinct()) + { + var semanticModel = compilation.GetSemanticModel(enumDeclaration.SyntaxTree); + var enumSymbol = semanticModel.GetDeclaredSymbol(enumDeclaration) as INamedTypeSymbol; + + // Check if the enum has the EnumLocalize attribute + if (enumSymbol?.GetAttributes().Any(ad => + ad.AttributeClass?.Name == Constants.EnumLocalizeAttributeName) ?? false) + { + GenerateSource(spc, enumSymbol, assemblyName); + } + } + } + + #endregion + + #region Generate Source + + private void GenerateSource(SourceProductionContext spc, INamedTypeSymbol enumSymbol, string assemblyName) + { + var enumFullName = enumSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var enumDataClassName = $"{enumSymbol.Name}{Constants.EnumLocalizeClassSuffix}"; + var enumName = enumSymbol.Name; + var enumNamespace = enumSymbol.ContainingNamespace.ToDisplayString(); + var tabString = Helper.Spacing(1); + + var sourceBuilder = new StringBuilder(); + + // Generate header + GeneratedHeaderFromPath(sourceBuilder, enumFullName); + sourceBuilder.AppendLine(); + + // Generate using directives + sourceBuilder.AppendLine("using System.Collections.Generic;"); + sourceBuilder.AppendLine("using Flow.Launcher.Plugin;"); + sourceBuilder.AppendLine(); + + // Generate namespace + sourceBuilder.AppendLine($"namespace {enumNamespace};"); + sourceBuilder.AppendLine(); + + // Generate class + sourceBuilder.AppendLine($"[System.CodeDom.Compiler.GeneratedCode(\"{nameof(EnumSourceGenerator)}\", \"{PackageVersion}\")]"); + sourceBuilder.AppendLine($"public class {enumDataClassName}"); + sourceBuilder.AppendLine("{"); + + // Generate properties + sourceBuilder.AppendLine($"{tabString}public {enumName} Value {{ get; private init; }}"); + sourceBuilder.AppendLine($"{tabString}public string Display {{ get; set; }}"); + sourceBuilder.AppendLine($"{tabString}public string LocalizationKey {{ get; set; }}"); + sourceBuilder.AppendLine($"{tabString}public string LocalizationValue {{ get; set; }}"); + sourceBuilder.AppendLine(); + + // Generate GetValues method + sourceBuilder.AppendLine($"{tabString}public static List<{enumDataClassName}> GetValues()"); + sourceBuilder.AppendLine($"{tabString}{{"); + sourceBuilder.AppendLine($"{tabString}{tabString}return new List<{enumDataClassName}>"); + sourceBuilder.AppendLine($"{tabString}{tabString}{{"); + var enumFields = GetEnumFields(spc, enumSymbol, enumFullName); + if (enumFields.Length == 0) return; + foreach (var enumField in enumFields) + { + GenerateEnumField(sourceBuilder, enumField, enumName, tabString); + } + sourceBuilder.AppendLine($"{tabString}{tabString}}};"); + sourceBuilder.AppendLine($"{tabString}}}"); + sourceBuilder.AppendLine(); + + // Generate UpdateLabels method + GenerateUpdateLabelsMethod(sourceBuilder, enumDataClassName, tabString); + + sourceBuilder.AppendLine($"}}"); + + // Add source to context + spc.AddSource($"{Constants.ClassName}.{enumNamespace}.{enumDataClassName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } + + private static void GeneratedHeaderFromPath(StringBuilder sb, string enumFullName) + { + if (string.IsNullOrEmpty(enumFullName)) + { + sb.AppendLine("/// "); + } + else + { + sb.AppendLine("/// ") + .AppendLine($"/// From: {enumFullName}") + .AppendLine("/// "); + } + } + + private static void GenerateEnumField(StringBuilder sb, EnumField enumField, string enumName, string tabString) + { + sb.AppendLine($"{tabString}{tabString}{tabString}new()"); + sb.AppendLine($"{tabString}{tabString}{tabString}{{"); + sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}Value = {enumName}.{enumField.EnumFieldName},"); + if (enumField.UseLocalizationKey) + { + // TODO + sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}Display = Main.Context.API.GetTranslation(\"{enumField.LocalizationKey}\"),"); + sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}LocalizationKey = \"{enumField.LocalizationKey}\","); + } + else + { + sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}Display = \"{enumField.LocalizationValue}\","); + } + if (enumField.LocalizationValue != null) + { + sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}LocalizationValue = \"{enumField.LocalizationValue}\","); + } + sb.AppendLine($"{tabString}{tabString}{tabString}}},"); + } + + private static void GenerateUpdateLabelsMethod(StringBuilder sb, string enumDataClassName, string tabString) + { + sb.AppendLine($"{tabString}public static void UpdateLabels(List<{enumDataClassName}> options)"); + sb.AppendLine($"{tabString}{{"); + sb.AppendLine($"{tabString}{tabString}foreach (var item in options)"); + sb.AppendLine($"{tabString}{tabString}{{"); + sb.AppendLine($"{tabString}{tabString}{tabString}if (!string.IsNullOrEmpty(item.LocalizationKey))"); + sb.AppendLine($"{tabString}{tabString}{tabString}{{"); + sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}item.Display = Main.Context.API.GetTranslation(item.LocalizationKey);"); + sb.AppendLine($"{tabString}{tabString}{tabString}}}"); + sb.AppendLine($"{tabString}{tabString}}}"); + sb.AppendLine($"{tabString}}}"); + } + + #endregion + + #region Get Enum Fields + + private static ImmutableArray GetEnumFields(SourceProductionContext spc, INamedTypeSymbol enumSymbol, string enumFullName) + { + // Iterate through enum members and get enum fields + var enumFields = new List(); + var enumError = false; + foreach (var member in enumSymbol.GetMembers().Where(m => m.Kind == SymbolKind.Field)) + { + if (member is IFieldSymbol fieldSymbol) + { + var enumFieldName = fieldSymbol.Name; + + // Check if the field has the EnumLocalizeKey attribute + var keyAttr = fieldSymbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.Name == Constants.EnumLocalizeKeyAttributeName); + var keyAttrExist = keyAttr != null; + + // Check if the field has the EnumLocalizeValue attribute + var valueAttr = fieldSymbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.Name == Constants.EnumLocalizeValueAttributeName); + var valueAttrExist = valueAttr != null; + + // Get the key and value from the attributes + var key = keyAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty; + var value = valueAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty; + + if (keyAttrExist && !string.IsNullOrEmpty(key)) + { + // If localization key exists and is valid, use it + enumFields.Add(new EnumField(enumFieldName, key, valueAttrExist ? value : null)); + } + else if (valueAttrExist) + { + // If localization value exists, use it + enumFields.Add(new EnumField(enumFieldName, value)); + } + else + { + // If localization key and value are not provided, do not generate the field and report a diagnostic + spc.ReportDiagnostic(Diagnostic.Create( + SourceGeneratorDiagnostics.EnumFieldLocalizationKeyValueInvalid, + Location.None, + $"{enumFullName}.{enumFieldName}")); + enumError = true; + } + } + } + + // If there was an error, do not generate the class + if (enumError) return _emptyEnumFields; + + return enumFields.ToImmutableArray(); + } + + #endregion + + #region Classes + + public class EnumField + { + public string EnumFieldName { get; set; } + public string LocalizationKey { get; set; } + public string LocalizationValue { get; set; } + + public bool UseLocalizationKey => LocalizationKey != null; + + public EnumField(string enumFieldName, string localizationValue) : this(enumFieldName, null, localizationValue) + { + } + + public EnumField(string enumFieldName, string localizationKey, string localizationValue) + { + EnumFieldName = enumFieldName; + LocalizationKey = localizationKey; + LocalizationValue = localizationValue; + } + } + + #endregion + } +} diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index 9b2e0ca..931426d 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -572,7 +572,7 @@ private static void GenerateSource( sourceBuilder.AppendLine($"public static class {Constants.ClassName}"); sourceBuilder.AppendLine("{"); - var tabString = Spacing(1); + var tabString = Helper.Spacing(1); // Generate API instance string getTranslation = null; @@ -677,20 +677,6 @@ private static void GenerateLocalizationMethod( sb.AppendLine(); } - private static string Spacing(int n) - { - Span spaces = stackalloc char[n * 4]; - spaces.Fill(' '); - - var sb = new StringBuilder(n * 4); - foreach (var c in spaces) - { - _ = sb.Append(c); - } - - return sb.ToString(); - } - #endregion #region Classes diff --git a/Flow.Launcher.Localization.SourceGenerators/SourceGeneratorDiagnostics.cs b/Flow.Launcher.Localization.SourceGenerators/SourceGeneratorDiagnostics.cs index d162cdb..f8cde85 100644 --- a/Flow.Launcher.Localization.SourceGenerators/SourceGeneratorDiagnostics.cs +++ b/Flow.Launcher.Localization.SourceGenerators/SourceGeneratorDiagnostics.cs @@ -67,5 +67,14 @@ public static class SourceGeneratorDiagnostics DiagnosticSeverity.Warning, isEnabledByDefault: true ); + + public static readonly DiagnosticDescriptor EnumFieldLocalizationKeyValueInvalid = new DiagnosticDescriptor( + "FLSG0008", + "Enum field localization key and value invalid", + $"Enum field `{{0}}` does not have a valid localization key or value", + "Localization", + DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); } } From e65572e2ccf55573b75ab879d2ab565e930c8f88 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 15:16:11 +0800 Subject: [PATCH 02/23] Fix namespaces --- .../Attributes/EnumLocalizeAttribute.cs | 2 +- .../Attributes/EnumLocalizeKeyAttribute.cs | 2 +- .../Attributes/EnumLocalizeValueAttribute.cs | 2 +- Flow.Launcher.Localization.Shared/Constants.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs index 8c98b89..5053ed1 100644 --- a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs +++ b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Flow.Launcher.Localization.SourceGenerators.Shared.Attributes +namespace Flow.Launcher.Localization.Shared.Attributes { /// /// Attribute to mark an enum for localization. diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs index de4a08f..df00e77 100644 --- a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs +++ b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Flow.Launcher.Localization.SourceGenerators.Shared.Attributes +namespace Flow.Launcher.Localization.Shared.Attributes { /// /// Attribute to mark a localization key for an enum field. diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs index 5af0499..74cf62b 100644 --- a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs +++ b/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Flow.Launcher.Localization.SourceGenerators.Shared.Attributes +namespace Flow.Launcher.Localization.Shared.Attributes { /// /// Attribute to mark a localization value for an enum field. diff --git a/Flow.Launcher.Localization.Shared/Constants.cs b/Flow.Launcher.Localization.Shared/Constants.cs index f5c9279..eddbdf5 100644 --- a/Flow.Launcher.Localization.Shared/Constants.cs +++ b/Flow.Launcher.Localization.Shared/Constants.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Localization.SourceGenerators.Shared.Attributes; +using Flow.Launcher.Localization.Shared.Attributes; using System.Text.RegularExpressions; namespace Flow.Launcher.Localization.Shared From fdbf20d73849c6c63389a7d81400dd9ab0ff05d1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 16:34:53 +0800 Subject: [PATCH 03/23] Add new attribution project --- .../EnumLocalizeAttribute.cs | 2 +- .../EnumLocalizeKeyAttribute.cs | 2 +- .../EnumLocalizeValueAttribute.cs | 2 +- .../Flow.Launcher.Localization.Attributes.csproj | 8 ++++++++ Flow.Launcher.Localization.Shared/Constants.cs | 9 ++++----- Flow.Launcher.Localization.slnx | 1 + 6 files changed, 16 insertions(+), 8 deletions(-) rename {Flow.Launcher.Localization.Shared/Attributes => Flow.Launcher.Localization.Attributes}/EnumLocalizeAttribute.cs (79%) rename {Flow.Launcher.Localization.Shared/Attributes => Flow.Launcher.Localization.Attributes}/EnumLocalizeKeyAttribute.cs (94%) rename {Flow.Launcher.Localization.Shared/Attributes => Flow.Launcher.Localization.Attributes}/EnumLocalizeValueAttribute.cs (94%) create mode 100644 Flow.Launcher.Localization.Attributes/Flow.Launcher.Localization.Attributes.csproj diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs b/Flow.Launcher.Localization.Attributes/EnumLocalizeAttribute.cs similarity index 79% rename from Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs rename to Flow.Launcher.Localization.Attributes/EnumLocalizeAttribute.cs index 5053ed1..21f2a06 100644 --- a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeAttribute.cs +++ b/Flow.Launcher.Localization.Attributes/EnumLocalizeAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Flow.Launcher.Localization.Shared.Attributes +namespace Flow.Launcher.Localization.Attributes { /// /// Attribute to mark an enum for localization. diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs b/Flow.Launcher.Localization.Attributes/EnumLocalizeKeyAttribute.cs similarity index 94% rename from Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs rename to Flow.Launcher.Localization.Attributes/EnumLocalizeKeyAttribute.cs index df00e77..39d75eb 100644 --- a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeKeyAttribute.cs +++ b/Flow.Launcher.Localization.Attributes/EnumLocalizeKeyAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Flow.Launcher.Localization.Shared.Attributes +namespace Flow.Launcher.Localization.Attributes { /// /// Attribute to mark a localization key for an enum field. diff --git a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs b/Flow.Launcher.Localization.Attributes/EnumLocalizeValueAttribute.cs similarity index 94% rename from Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs rename to Flow.Launcher.Localization.Attributes/EnumLocalizeValueAttribute.cs index 74cf62b..75c3859 100644 --- a/Flow.Launcher.Localization.Shared/Attributes/EnumLocalizeValueAttribute.cs +++ b/Flow.Launcher.Localization.Attributes/EnumLocalizeValueAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Flow.Launcher.Localization.Shared.Attributes +namespace Flow.Launcher.Localization.Attributes { /// /// Attribute to mark a localization value for an enum field. diff --git a/Flow.Launcher.Localization.Attributes/Flow.Launcher.Localization.Attributes.csproj b/Flow.Launcher.Localization.Attributes/Flow.Launcher.Localization.Attributes.csproj new file mode 100644 index 0000000..8cae302 --- /dev/null +++ b/Flow.Launcher.Localization.Attributes/Flow.Launcher.Localization.Attributes.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.0 + Flow.Launcher.Localization.Attribute + + + diff --git a/Flow.Launcher.Localization.Shared/Constants.cs b/Flow.Launcher.Localization.Shared/Constants.cs index eddbdf5..a4610ec 100644 --- a/Flow.Launcher.Localization.Shared/Constants.cs +++ b/Flow.Launcher.Localization.Shared/Constants.cs @@ -1,5 +1,4 @@ -using Flow.Launcher.Localization.Shared.Attributes; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace Flow.Launcher.Localization.Shared { @@ -22,9 +21,9 @@ public static class Constants public const string StringFormatMethodName = "Format"; public const string StringFormatTypeName = "string"; public const string EnumLocalizeClassSuffix = "Data"; - public const string EnumLocalizeAttributeName = nameof(EnumLocalizeAttribute); - public const string EnumLocalizeKeyAttributeName = nameof(EnumLocalizeKeyAttribute); - public const string EnumLocalizeValueAttributeName = nameof(EnumLocalizeValueAttribute); + public const string EnumLocalizeAttributeName = "EnumLocalizeAttribute"; + public const string EnumLocalizeKeyAttributeName = "EnumLocalizeKeyAttribute"; + public const string EnumLocalizeValueAttributeName = "EnumLocalizeValueAttribute"; public static readonly Regex LanguagesXamlRegex = new Regex(@"\\Languages\\[^\\]+\.xaml$", RegexOptions.IgnoreCase); public static readonly string[] OldLocalizationClasses = { "IPublicAPI", "Internationalization" }; diff --git a/Flow.Launcher.Localization.slnx b/Flow.Launcher.Localization.slnx index 27c8f44..779a1ca 100644 --- a/Flow.Launcher.Localization.slnx +++ b/Flow.Launcher.Localization.slnx @@ -1,5 +1,6 @@ + From 4e993c37776d4a63d835cf3451d4e6cd84caa7b3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 16:58:50 +0800 Subject: [PATCH 04/23] Include Flow.Launcher.Localization.Attributes project --- .../Flow.Launcher.Localization.csproj | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Localization/Flow.Launcher.Localization.csproj b/Flow.Launcher.Localization/Flow.Launcher.Localization.csproj index b9ee6bc..5cf25ff 100644 --- a/Flow.Launcher.Localization/Flow.Launcher.Localization.csproj +++ b/Flow.Launcher.Localization/Flow.Launcher.Localization.csproj @@ -6,7 +6,7 @@ Flow.Launcher.Localization Flow.Launcher.Localization false - true + false true true @@ -33,6 +33,9 @@ All + + runtime + All @@ -47,6 +50,11 @@ analyzers/dotnet/cs false + + true + lib/$(TargetFramework) + true + true analyzers/dotnet/cs From f2bc91d642fc57034d28b2be587f3965b95ced98 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 17:36:51 +0800 Subject: [PATCH 05/23] Add summary for classes, properties and methods --- .../Localize/EnumSourceGenerator.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 88a73cc..8d263c2 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -98,18 +98,42 @@ private void GenerateSource(SourceProductionContext spc, INamedTypeSymbol enumSy sourceBuilder.AppendLine(); // Generate class + sourceBuilder.AppendLine($"/// "); + sourceBuilder.AppendLine($"/// Data class for "); + sourceBuilder.AppendLine($"/// "); sourceBuilder.AppendLine($"[System.CodeDom.Compiler.GeneratedCode(\"{nameof(EnumSourceGenerator)}\", \"{PackageVersion}\")]"); sourceBuilder.AppendLine($"public class {enumDataClassName}"); sourceBuilder.AppendLine("{"); // Generate properties + sourceBuilder.AppendLine($"{tabString}/// "); + sourceBuilder.AppendLine($"{tabString}/// The value of the enum"); + sourceBuilder.AppendLine($"{tabString}/// "); sourceBuilder.AppendLine($"{tabString}public {enumName} Value {{ get; private init; }}"); + sourceBuilder.AppendLine(); + + sourceBuilder.AppendLine($"{tabString}/// "); + sourceBuilder.AppendLine($"{tabString}/// The display text of the enum value"); + sourceBuilder.AppendLine($"{tabString}/// "); sourceBuilder.AppendLine($"{tabString}public string Display {{ get; set; }}"); + sourceBuilder.AppendLine(); + + sourceBuilder.AppendLine($"{tabString}/// "); + sourceBuilder.AppendLine($"{tabString}/// The localization key of the enum value"); + sourceBuilder.AppendLine($"{tabString}/// "); sourceBuilder.AppendLine($"{tabString}public string LocalizationKey {{ get; set; }}"); + sourceBuilder.AppendLine(); + + sourceBuilder.AppendLine($"{tabString}/// "); + sourceBuilder.AppendLine($"{tabString}/// The localization value of the enum value"); + sourceBuilder.AppendLine($"{tabString}/// "); sourceBuilder.AppendLine($"{tabString}public string LocalizationValue {{ get; set; }}"); sourceBuilder.AppendLine(); // Generate GetValues method + sourceBuilder.AppendLine($"{tabString}/// "); + sourceBuilder.AppendLine($"{tabString}/// Get all values of "); + sourceBuilder.AppendLine($"{tabString}/// "); sourceBuilder.AppendLine($"{tabString}public static List<{enumDataClassName}> GetValues()"); sourceBuilder.AppendLine($"{tabString}{{"); sourceBuilder.AppendLine($"{tabString}{tabString}return new List<{enumDataClassName}>"); @@ -171,6 +195,10 @@ private static void GenerateEnumField(StringBuilder sb, EnumField enumField, str private static void GenerateUpdateLabelsMethod(StringBuilder sb, string enumDataClassName, string tabString) { + sb.AppendLine($"{tabString}/// "); + sb.AppendLine($"{tabString}/// Update the labels of the enum values when culture info changes."); + sb.AppendLine($"{tabString}/// See for more details"); + sb.AppendLine($"{tabString}/// "); sb.AppendLine($"{tabString}public static void UpdateLabels(List<{enumDataClassName}> options)"); sb.AppendLine($"{tabString}{{"); sb.AppendLine($"{tabString}{tabString}foreach (var item in options)"); From a47ce440a219fdbf1c7007067236ab4a204671b1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 17:51:32 +0800 Subject: [PATCH 06/23] Fix root namespace --- .../Flow.Launcher.Localization.Attributes.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Localization.Attributes/Flow.Launcher.Localization.Attributes.csproj b/Flow.Launcher.Localization.Attributes/Flow.Launcher.Localization.Attributes.csproj index 8cae302..dd973d9 100644 --- a/Flow.Launcher.Localization.Attributes/Flow.Launcher.Localization.Attributes.csproj +++ b/Flow.Launcher.Localization.Attributes/Flow.Launcher.Localization.Attributes.csproj @@ -2,7 +2,7 @@ netstandard2.0 - Flow.Launcher.Localization.Attribute + Flow.Launcher.Localization.Attributes From 5a92b4b68b3c01c3455cf97f28fef104411a0f31 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 17:52:24 +0800 Subject: [PATCH 07/23] Use global namespace --- .../Localize/EnumSourceGenerator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 8d263c2..75650e3 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -90,7 +90,6 @@ private void GenerateSource(SourceProductionContext spc, INamedTypeSymbol enumSy // Generate using directives sourceBuilder.AppendLine("using System.Collections.Generic;"); - sourceBuilder.AppendLine("using Flow.Launcher.Plugin;"); sourceBuilder.AppendLine(); // Generate namespace @@ -197,7 +196,7 @@ private static void GenerateUpdateLabelsMethod(StringBuilder sb, string enumData { sb.AppendLine($"{tabString}/// "); sb.AppendLine($"{tabString}/// Update the labels of the enum values when culture info changes."); - sb.AppendLine($"{tabString}/// See for more details"); + sb.AppendLine($"{tabString}/// See for more details"); sb.AppendLine($"{tabString}/// "); sb.AppendLine($"{tabString}public static void UpdateLabels(List<{enumDataClassName}> options)"); sb.AppendLine($"{tabString}{{"); From 2baa32f820957c90f1ae40fdb59a765c6e9995e2 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 18:05:57 +0800 Subject: [PATCH 08/23] Move get valid plugin info to shared space --- .../Localize/LocalizeSourceGenerator.cs | 86 +----------------- .../PluginInfoHelper.cs | 88 +++++++++++++++++++ 2 files changed, 91 insertions(+), 83 deletions(-) create mode 100644 Flow.Launcher.Localization.SourceGenerators/PluginInfoHelper.cs diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index 931426d..a4af84b 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -100,7 +100,9 @@ private void Execute(SourceProductionContext spc, var assemblyName = compilation.AssemblyName ?? Constants.DefaultNamespace; var useDI = configOptions.GetFLLUseDependencyInjection(); - var pluginInfo = GetValidPluginInfo(pluginClasses, spc, useDI); + var pluginInfo = PluginInfoHelper.GetValidPluginInfoAndReportDiagnostic(pluginClasses, spc, useDI); + + if (pluginInfo == null) return; GenerateSource( spc, @@ -421,88 +423,6 @@ private static string GetLocalizationKeyFromInvocation(GeneratorSyntaxContext co #endregion - #region Get Plugin Class Info - - private static PluginClassInfo GetValidPluginInfo( - ImmutableArray pluginClasses, - SourceProductionContext context, - bool useDI) - { - // If p is null, this class does not implement IPluginI18n - var iPluginI18nClasses = pluginClasses.Where(p => p != null).ToArray(); - if (iPluginI18nClasses.Length == 0) - { - context.ReportDiagnostic(Diagnostic.Create( - SourceGeneratorDiagnostics.CouldNotFindPluginEntryClass, - Location.None - )); - return null; - } - - // If we use dependency injection, we do not need to check if there is a valid plugin context - // Also we do not need to return the plugin info - if (useDI) - { - return null; - } - - // If p.PropertyName is null, this class does not have PluginInitContext property - var iPluginI18nClassesWithContext = iPluginI18nClasses.Where(p => p.PropertyName != null).ToArray(); - if (iPluginI18nClassesWithContext.Length == 0) - { - foreach (var pluginClass in iPluginI18nClasses) - { - context.ReportDiagnostic(Diagnostic.Create( - SourceGeneratorDiagnostics.CouldNotFindContextProperty, - pluginClass.Location, - pluginClass.ClassName - )); - } - return null; - } - - // Rest classes have implemented IPluginI18n and have PluginInitContext property - // Check if the property is valid - foreach (var pluginClass in iPluginI18nClassesWithContext) - { - if (pluginClass.IsValid == true) - { - return pluginClass; - } - - if (!pluginClass.IsStatic) - { - context.ReportDiagnostic(Diagnostic.Create( - SourceGeneratorDiagnostics.ContextPropertyNotStatic, - pluginClass.Location, - pluginClass.PropertyName - )); - } - - if (pluginClass.IsPrivate) - { - context.ReportDiagnostic(Diagnostic.Create( - SourceGeneratorDiagnostics.ContextPropertyIsPrivate, - pluginClass.Location, - pluginClass.PropertyName - )); - } - - if (pluginClass.IsProtected) - { - context.ReportDiagnostic(Diagnostic.Create( - SourceGeneratorDiagnostics.ContextPropertyIsProtected, - pluginClass.Location, - pluginClass.PropertyName - )); - } - } - - return null; - } - - #endregion - #region Generate Source private static void GenerateSource( diff --git a/Flow.Launcher.Localization.SourceGenerators/PluginInfoHelper.cs b/Flow.Launcher.Localization.SourceGenerators/PluginInfoHelper.cs new file mode 100644 index 0000000..7a77103 --- /dev/null +++ b/Flow.Launcher.Localization.SourceGenerators/PluginInfoHelper.cs @@ -0,0 +1,88 @@ +using System.Collections.Immutable; +using System.Linq; +using Flow.Launcher.Localization.Shared; +using Microsoft.CodeAnalysis; + +namespace Flow.Launcher.Localization.SourceGenerators +{ + internal class PluginInfoHelper + { + public static PluginClassInfo GetValidPluginInfoAndReportDiagnostic( + ImmutableArray pluginClasses, + SourceProductionContext context, + bool useDI) + { + // If p is null, this class does not implement IPluginI18n + var iPluginI18nClasses = pluginClasses.Where(p => p != null).ToArray(); + if (iPluginI18nClasses.Length == 0) + { + context.ReportDiagnostic(Diagnostic.Create( + SourceGeneratorDiagnostics.CouldNotFindPluginEntryClass, + Location.None + )); + return null; + } + + // If we use dependency injection, we do not need to check if there is a valid plugin context + // Also we do not need to return the plugin info + if (useDI) + { + return null; + } + + // If p.PropertyName is null, this class does not have PluginInitContext property + var iPluginI18nClassesWithContext = iPluginI18nClasses.Where(p => p.PropertyName != null).ToArray(); + if (iPluginI18nClassesWithContext.Length == 0) + { + foreach (var pluginClass in iPluginI18nClasses) + { + context.ReportDiagnostic(Diagnostic.Create( + SourceGeneratorDiagnostics.CouldNotFindContextProperty, + pluginClass.Location, + pluginClass.ClassName + )); + } + return null; + } + + // Rest classes have implemented IPluginI18n and have PluginInitContext property + // Check if the property is valid + foreach (var pluginClass in iPluginI18nClassesWithContext) + { + if (pluginClass.IsValid == true) + { + return pluginClass; + } + + if (!pluginClass.IsStatic) + { + context.ReportDiagnostic(Diagnostic.Create( + SourceGeneratorDiagnostics.ContextPropertyNotStatic, + pluginClass.Location, + pluginClass.PropertyName + )); + } + + if (pluginClass.IsPrivate) + { + context.ReportDiagnostic(Diagnostic.Create( + SourceGeneratorDiagnostics.ContextPropertyIsPrivate, + pluginClass.Location, + pluginClass.PropertyName + )); + } + + if (pluginClass.IsProtected) + { + context.ReportDiagnostic(Diagnostic.Create( + SourceGeneratorDiagnostics.ContextPropertyIsProtected, + pluginClass.Location, + pluginClass.PropertyName + )); + } + } + + return null; + } + } +} From 49c12b554f1fc5a56864b0651edd582eec7160bd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 18:08:37 +0800 Subject: [PATCH 09/23] Support useDI & Fix api translation issue --- .../Localize/EnumSourceGenerator.cs | 75 +++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 75650e3..48f94a8 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -6,6 +6,7 @@ using Flow.Launcher.Localization.Shared; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; namespace Flow.Launcher.Localization.SourceGenerators.Localize @@ -36,9 +37,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Where(ed => ed.AttributeLists.Count > 0) .Collect(); + var pluginClasses = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: (n, _) => n is ClassDeclarationSyntax, + transform: (c, t) => Helper.GetPluginClassInfo((ClassDeclarationSyntax)c.Node, c.SemanticModel, t)) + .Where(info => info != null) + .Collect(); + var compilation = context.CompilationProvider; - var compilationEnums = enumDeclarations.Combine(compilation); + var configOptions = context.AnalyzerConfigOptionsProvider; + + var compilationEnums = enumDeclarations.Combine(pluginClasses).Combine(configOptions).Combine(compilation); context.RegisterSourceOutput(compilationEnums, Execute); } @@ -49,14 +59,24 @@ public void Initialize(IncrementalGeneratorInitializationContext context) /// The source production context. /// The provided data. private void Execute(SourceProductionContext spc, - (ImmutableArray Enums, Compilation Compilation) data) + (((ImmutableArray EnumsDeclarations, + ImmutableArray PluginClassInfos), + AnalyzerConfigOptionsProvider ConfigOptionsProvider), + Compilation Compilation) data) { - var enums = data.Enums; var compilation = data.Compilation; + var configOptions = data.Item1.ConfigOptionsProvider; + var pluginClasses = data.Item1.Item1.PluginClassInfos; + var enumsDeclarations = data.Item1.Item1.EnumsDeclarations; var assemblyName = compilation.AssemblyName ?? Constants.DefaultNamespace; + var useDI = configOptions.GetFLLUseDependencyInjection(); + + var pluginInfo = PluginInfoHelper.GetValidPluginInfoAndReportDiagnostic(pluginClasses, spc, useDI); + + if (pluginInfo == null) return; - foreach (var enumDeclaration in enums.Distinct()) + foreach (var enumDeclaration in enumsDeclarations.Distinct()) { var semanticModel = compilation.GetSemanticModel(enumDeclaration.SyntaxTree); var enumSymbol = semanticModel.GetDeclaredSymbol(enumDeclaration) as INamedTypeSymbol; @@ -65,7 +85,7 @@ private void Execute(SourceProductionContext spc, if (enumSymbol?.GetAttributes().Any(ad => ad.AttributeClass?.Name == Constants.EnumLocalizeAttributeName) ?? false) { - GenerateSource(spc, enumSymbol, assemblyName); + GenerateSource(spc, enumSymbol, useDI, pluginInfo, assemblyName); } } } @@ -74,7 +94,12 @@ private void Execute(SourceProductionContext spc, #region Generate Source - private void GenerateSource(SourceProductionContext spc, INamedTypeSymbol enumSymbol, string assemblyName) + private void GenerateSource( + SourceProductionContext spc, + INamedTypeSymbol enumSymbol, + bool useDI, + PluginClassInfo pluginInfo, + string assemblyName) { var enumFullName = enumSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var enumDataClassName = $"{enumSymbol.Name}{Constants.EnumLocalizeClassSuffix}"; @@ -129,6 +154,20 @@ private void GenerateSource(SourceProductionContext spc, INamedTypeSymbol enumSy sourceBuilder.AppendLine($"{tabString}public string LocalizationValue {{ get; set; }}"); sourceBuilder.AppendLine(); + // Generate API instance + string getTranslation = null; + if (useDI) + { + sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI? api = null;"); + sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI Api => api ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService();"); + sourceBuilder.AppendLine(); + getTranslation = "Api.GetTranslation"; + } + else if (pluginInfo?.IsValid == true) + { + getTranslation = $"{pluginInfo.ContextAccessor}.API.GetTranslation"; + } + // Generate GetValues method sourceBuilder.AppendLine($"{tabString}/// "); sourceBuilder.AppendLine($"{tabString}/// Get all values of "); @@ -141,19 +180,19 @@ private void GenerateSource(SourceProductionContext spc, INamedTypeSymbol enumSy if (enumFields.Length == 0) return; foreach (var enumField in enumFields) { - GenerateEnumField(sourceBuilder, enumField, enumName, tabString); + GenerateEnumField(sourceBuilder, getTranslation, enumField, enumName, tabString); } sourceBuilder.AppendLine($"{tabString}{tabString}}};"); sourceBuilder.AppendLine($"{tabString}}}"); sourceBuilder.AppendLine(); // Generate UpdateLabels method - GenerateUpdateLabelsMethod(sourceBuilder, enumDataClassName, tabString); + GenerateUpdateLabelsMethod(sourceBuilder, getTranslation, enumDataClassName, tabString); sourceBuilder.AppendLine($"}}"); // Add source to context - spc.AddSource($"{Constants.ClassName}.{enumNamespace}.{enumDataClassName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + spc.AddSource($"{Constants.ClassName}.{assemblyName}.{enumNamespace}.{enumDataClassName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } private static void GeneratedHeaderFromPath(StringBuilder sb, string enumFullName) @@ -170,15 +209,19 @@ private static void GeneratedHeaderFromPath(StringBuilder sb, string enumFullNam } } - private static void GenerateEnumField(StringBuilder sb, EnumField enumField, string enumName, string tabString) + private static void GenerateEnumField( + StringBuilder sb, + string getTranslation, + EnumField enumField, + string enumName, + string tabString) { sb.AppendLine($"{tabString}{tabString}{tabString}new()"); sb.AppendLine($"{tabString}{tabString}{tabString}{{"); sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}Value = {enumName}.{enumField.EnumFieldName},"); if (enumField.UseLocalizationKey) { - // TODO - sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}Display = Main.Context.API.GetTranslation(\"{enumField.LocalizationKey}\"),"); + sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}Display = {getTranslation}(\"{enumField.LocalizationKey}\"),"); sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}LocalizationKey = \"{enumField.LocalizationKey}\","); } else @@ -192,7 +235,11 @@ private static void GenerateEnumField(StringBuilder sb, EnumField enumField, str sb.AppendLine($"{tabString}{tabString}{tabString}}},"); } - private static void GenerateUpdateLabelsMethod(StringBuilder sb, string enumDataClassName, string tabString) + private static void GenerateUpdateLabelsMethod( + StringBuilder sb, + string getTranslation, + string enumDataClassName, + string tabString) { sb.AppendLine($"{tabString}/// "); sb.AppendLine($"{tabString}/// Update the labels of the enum values when culture info changes."); @@ -204,7 +251,7 @@ private static void GenerateUpdateLabelsMethod(StringBuilder sb, string enumData sb.AppendLine($"{tabString}{tabString}{{"); sb.AppendLine($"{tabString}{tabString}{tabString}if (!string.IsNullOrEmpty(item.LocalizationKey))"); sb.AppendLine($"{tabString}{tabString}{tabString}{{"); - sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}item.Display = Main.Context.API.GetTranslation(item.LocalizationKey);"); + sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}item.Display = {getTranslation}(item.LocalizationKey);"); sb.AppendLine($"{tabString}{tabString}{tabString}}}"); sb.AppendLine($"{tabString}{tabString}}}"); sb.AppendLine($"{tabString}}}"); From b6d291a57d4180554029b52678502aa364c463de Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 18:08:54 +0800 Subject: [PATCH 10/23] Code quality --- .../Localize/EnumSourceGenerator.cs | 114 +++++++++--------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 48f94a8..6c9d168 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -92,6 +92,63 @@ private void Execute(SourceProductionContext spc, #endregion + #region Get Enum Fields + + private static ImmutableArray GetEnumFields(SourceProductionContext spc, INamedTypeSymbol enumSymbol, string enumFullName) + { + // Iterate through enum members and get enum fields + var enumFields = new List(); + var enumError = false; + foreach (var member in enumSymbol.GetMembers().Where(m => m.Kind == SymbolKind.Field)) + { + if (member is IFieldSymbol fieldSymbol) + { + var enumFieldName = fieldSymbol.Name; + + // Check if the field has the EnumLocalizeKey attribute + var keyAttr = fieldSymbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.Name == Constants.EnumLocalizeKeyAttributeName); + var keyAttrExist = keyAttr != null; + + // Check if the field has the EnumLocalizeValue attribute + var valueAttr = fieldSymbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.Name == Constants.EnumLocalizeValueAttributeName); + var valueAttrExist = valueAttr != null; + + // Get the key and value from the attributes + var key = keyAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty; + var value = valueAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty; + + if (keyAttrExist && !string.IsNullOrEmpty(key)) + { + // If localization key exists and is valid, use it + enumFields.Add(new EnumField(enumFieldName, key, valueAttrExist ? value : null)); + } + else if (valueAttrExist) + { + // If localization value exists, use it + enumFields.Add(new EnumField(enumFieldName, value)); + } + else + { + // If localization key and value are not provided, do not generate the field and report a diagnostic + spc.ReportDiagnostic(Diagnostic.Create( + SourceGeneratorDiagnostics.EnumFieldLocalizationKeyValueInvalid, + Location.None, + $"{enumFullName}.{enumFieldName}")); + enumError = true; + } + } + } + + // If there was an error, do not generate the class + if (enumError) return _emptyEnumFields; + + return enumFields.ToImmutableArray(); + } + + #endregion + #region Generate Source private void GenerateSource( @@ -259,63 +316,6 @@ private static void GenerateUpdateLabelsMethod( #endregion - #region Get Enum Fields - - private static ImmutableArray GetEnumFields(SourceProductionContext spc, INamedTypeSymbol enumSymbol, string enumFullName) - { - // Iterate through enum members and get enum fields - var enumFields = new List(); - var enumError = false; - foreach (var member in enumSymbol.GetMembers().Where(m => m.Kind == SymbolKind.Field)) - { - if (member is IFieldSymbol fieldSymbol) - { - var enumFieldName = fieldSymbol.Name; - - // Check if the field has the EnumLocalizeKey attribute - var keyAttr = fieldSymbol.GetAttributes() - .FirstOrDefault(a => a.AttributeClass?.Name == Constants.EnumLocalizeKeyAttributeName); - var keyAttrExist = keyAttr != null; - - // Check if the field has the EnumLocalizeValue attribute - var valueAttr = fieldSymbol.GetAttributes() - .FirstOrDefault(a => a.AttributeClass?.Name == Constants.EnumLocalizeValueAttributeName); - var valueAttrExist = valueAttr != null; - - // Get the key and value from the attributes - var key = keyAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty; - var value = valueAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty; - - if (keyAttrExist && !string.IsNullOrEmpty(key)) - { - // If localization key exists and is valid, use it - enumFields.Add(new EnumField(enumFieldName, key, valueAttrExist ? value : null)); - } - else if (valueAttrExist) - { - // If localization value exists, use it - enumFields.Add(new EnumField(enumFieldName, value)); - } - else - { - // If localization key and value are not provided, do not generate the field and report a diagnostic - spc.ReportDiagnostic(Diagnostic.Create( - SourceGeneratorDiagnostics.EnumFieldLocalizationKeyValueInvalid, - Location.None, - $"{enumFullName}.{enumFieldName}")); - enumError = true; - } - } - } - - // If there was an error, do not generate the class - if (enumError) return _emptyEnumFields; - - return enumFields.ToImmutableArray(); - } - - #endregion - #region Classes public class EnumField From 9836cff2e036f7ce4262e60c60cd8e6db606db1d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 18:17:56 +0800 Subject: [PATCH 11/23] Add assembly version for source generator project --- .../Flow.Launcher.Localization.SourceGenerators.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj b/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj index 1a95f4d..90bd0d3 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj +++ b/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj @@ -4,6 +4,8 @@ netstandard2.0 true Flow.Launcher.Localization.SourceGenerators + + 0.2.15 From 13dfbb294766e59ab20f6b087a85ca753b71e1db Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 19:03:44 +0800 Subject: [PATCH 12/23] Change local variable name --- .../Localize/EnumSourceGenerator.cs | 10 +++++----- .../Localize/LocalizeSourceGenerator.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 6c9d168..ff33b00 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -69,7 +69,7 @@ private void Execute(SourceProductionContext spc, var pluginClasses = data.Item1.Item1.PluginClassInfos; var enumsDeclarations = data.Item1.Item1.EnumsDeclarations; - var assemblyName = compilation.AssemblyName ?? Constants.DefaultNamespace; + var assemblyNamespace = compilation.AssemblyName ?? Constants.DefaultNamespace; var useDI = configOptions.GetFLLUseDependencyInjection(); var pluginInfo = PluginInfoHelper.GetValidPluginInfoAndReportDiagnostic(pluginClasses, spc, useDI); @@ -85,7 +85,7 @@ private void Execute(SourceProductionContext spc, if (enumSymbol?.GetAttributes().Any(ad => ad.AttributeClass?.Name == Constants.EnumLocalizeAttributeName) ?? false) { - GenerateSource(spc, enumSymbol, useDI, pluginInfo, assemblyName); + GenerateSource(spc, enumSymbol, useDI, pluginInfo, assemblyNamespace); } } } @@ -156,7 +156,7 @@ private void GenerateSource( INamedTypeSymbol enumSymbol, bool useDI, PluginClassInfo pluginInfo, - string assemblyName) + string assemblyNamespace) { var enumFullName = enumSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var enumDataClassName = $"{enumSymbol.Name}{Constants.EnumLocalizeClassSuffix}"; @@ -222,7 +222,7 @@ private void GenerateSource( } else if (pluginInfo?.IsValid == true) { - getTranslation = $"{pluginInfo.ContextAccessor}.API.GetTranslation"; + getTranslation = $"{assemblyNamespace}.{pluginInfo.ContextAccessor}.API.GetTranslation"; } // Generate GetValues method @@ -249,7 +249,7 @@ private void GenerateSource( sourceBuilder.AppendLine($"}}"); // Add source to context - spc.AddSource($"{Constants.ClassName}.{assemblyName}.{enumNamespace}.{enumDataClassName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + spc.AddSource($"{Constants.ClassName}.{assemblyNamespace}.{enumNamespace}.{enumDataClassName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } private static void GeneratedHeaderFromPath(StringBuilder sb, string enumFullName) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index a4af84b..5fd06ac 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -97,7 +97,7 @@ private void Execute(SourceProductionContext spc, var usedKeys = data.Item1.Item1.Item1.Item1.InvocationKeys; var localizedStrings = data.Item1.Item1.Item1.Item1.LocalizableStrings; - var assemblyName = compilation.AssemblyName ?? Constants.DefaultNamespace; + var assemblyNamespace = compilation.AssemblyName ?? Constants.DefaultNamespace; var useDI = configOptions.GetFLLUseDependencyInjection(); var pluginInfo = PluginInfoHelper.GetValidPluginInfoAndReportDiagnostic(pluginClasses, spc, useDI); @@ -108,7 +108,7 @@ private void Execute(SourceProductionContext spc, spc, xamlFiles[0], localizedStrings, - assemblyName, + assemblyNamespace, useDI, pluginInfo, usedKeys); @@ -429,7 +429,7 @@ private static void GenerateSource( SourceProductionContext spc, AdditionalText xamlFile, ImmutableArray localizedStrings, - string assemblyName, + string assemblyNamespace, bool useDI, PluginClassInfo pluginInfo, IEnumerable usedKeys) @@ -458,7 +458,7 @@ private static void GenerateSource( sourceBuilder.AppendLine(); // Generate namespace - sourceBuilder.AppendLine($"namespace {assemblyName};"); + sourceBuilder.AppendLine($"namespace {assemblyNamespace};"); sourceBuilder.AppendLine(); // Uncomment them for debugging @@ -518,7 +518,7 @@ private static void GenerateSource( sourceBuilder.AppendLine("}"); // Add source to context - spc.AddSource($"{Constants.ClassName}.{assemblyName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + spc.AddSource($"{Constants.ClassName}.{assemblyNamespace}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } private static void GeneratedHeaderFromPath(StringBuilder sb, string xamlFilePath) From 81f4889a97e5e20777ca21e761974ed91b714e61 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 19:04:21 +0800 Subject: [PATCH 13/23] Use one Api --- .../Localize/EnumSourceGenerator.cs | 6 ++---- .../Localize/LocalizeSourceGenerator.cs | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index ff33b00..5163e48 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -215,10 +215,8 @@ private void GenerateSource( string getTranslation = null; if (useDI) { - sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI? api = null;"); - sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI Api => api ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService();"); - sourceBuilder.AppendLine(); - getTranslation = "Api.GetTranslation"; + // Use Api from LocalizeSourceGenerator + getTranslation = $"{assemblyNamespace}.{Constants.ClassName}.Api.GetTranslation"; } else if (pluginInfo?.IsValid == true) { diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index 5fd06ac..9e6b3d0 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -499,7 +499,8 @@ private static void GenerateSource( if (useDI) { sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI? api = null;"); - sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI Api => api ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService();"); + // Internal for EnumSourceGenerator to use + sourceBuilder.AppendLine($"{tabString}internal static Flow.Launcher.Plugin.IPublicAPI Api => api ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService();"); sourceBuilder.AppendLine(); getTranslation = "Api.GetTranslation"; } From f79cbb98ab77045ab09ba93f3593f30635402669 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 19:33:18 +0800 Subject: [PATCH 14/23] Fix useDI issue --- .../Localize/EnumSourceGenerator.cs | 19 ++++++++++++++++--- .../Localize/LocalizeSourceGenerator.cs | 19 ++++++++++++++++--- .../PluginInfoHelper.cs | 10 +--------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 5163e48..84cf171 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -72,9 +72,22 @@ private void Execute(SourceProductionContext spc, var assemblyNamespace = compilation.AssemblyName ?? Constants.DefaultNamespace; var useDI = configOptions.GetFLLUseDependencyInjection(); - var pluginInfo = PluginInfoHelper.GetValidPluginInfoAndReportDiagnostic(pluginClasses, spc, useDI); - - if (pluginInfo == null) return; + PluginClassInfo pluginInfo; + if (useDI) + { + // If we use dependency injection, we do not need to check if there is a valid plugin context + // Also we do not need to return the plugin info + pluginInfo = null; + } + else + { + pluginInfo = PluginInfoHelper.GetValidPluginInfoAndReportDiagnostic(pluginClasses, spc); + if (pluginInfo == null) + { + // If we cannot find a valid plugin info, we do not need to generate the source + return; + } + } foreach (var enumDeclaration in enumsDeclarations.Distinct()) { diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index 9e6b3d0..3c9a387 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -100,9 +100,22 @@ private void Execute(SourceProductionContext spc, var assemblyNamespace = compilation.AssemblyName ?? Constants.DefaultNamespace; var useDI = configOptions.GetFLLUseDependencyInjection(); - var pluginInfo = PluginInfoHelper.GetValidPluginInfoAndReportDiagnostic(pluginClasses, spc, useDI); - - if (pluginInfo == null) return; + PluginClassInfo pluginInfo; + if (useDI) + { + // If we use dependency injection, we do not need to check if there is a valid plugin context + // Also we do not need to return the plugin info + pluginInfo = null; + } + else + { + pluginInfo = PluginInfoHelper.GetValidPluginInfoAndReportDiagnostic(pluginClasses, spc); + if (pluginInfo == null) + { + // If we cannot find a valid plugin info, we do not need to generate the source + return; + } + } GenerateSource( spc, diff --git a/Flow.Launcher.Localization.SourceGenerators/PluginInfoHelper.cs b/Flow.Launcher.Localization.SourceGenerators/PluginInfoHelper.cs index 7a77103..a26ee57 100644 --- a/Flow.Launcher.Localization.SourceGenerators/PluginInfoHelper.cs +++ b/Flow.Launcher.Localization.SourceGenerators/PluginInfoHelper.cs @@ -9,8 +9,7 @@ internal class PluginInfoHelper { public static PluginClassInfo GetValidPluginInfoAndReportDiagnostic( ImmutableArray pluginClasses, - SourceProductionContext context, - bool useDI) + SourceProductionContext context) { // If p is null, this class does not implement IPluginI18n var iPluginI18nClasses = pluginClasses.Where(p => p != null).ToArray(); @@ -23,13 +22,6 @@ public static PluginClassInfo GetValidPluginInfoAndReportDiagnostic( return null; } - // If we use dependency injection, we do not need to check if there is a valid plugin context - // Also we do not need to return the plugin info - if (useDI) - { - return null; - } - // If p.PropertyName is null, this class does not have PluginInitContext property var iPluginI18nClassesWithContext = iPluginI18nClasses.Where(p => p.PropertyName != null).ToArray(); if (iPluginI18nClassesWithContext.Length == 0) From 107e4eebfdcecb4f11cd54b86d0c847e9789442f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 19:44:43 +0800 Subject: [PATCH 15/23] Fix code comments --- .../Localize/EnumSourceGenerator.cs | 5 ++--- .../Localize/LocalizeSourceGenerator.cs | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 84cf171..2b031ca 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -25,7 +25,7 @@ public partial class EnumSourceGenerator : IIncrementalGenerator #region Incremental Generator /// - /// Initializes the generator and registers source output based on resource files. + /// Initializes the generator and registers source output based on enum declarations. /// /// The initialization context. public void Initialize(IncrementalGeneratorInitializationContext context) @@ -54,7 +54,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } /// - /// Executes the generation of string properties based on the provided data. + /// Executes the generation of enum data classes based on the provided data. /// /// The source production context. /// The provided data. @@ -76,7 +76,6 @@ private void Execute(SourceProductionContext spc, if (useDI) { // If we use dependency injection, we do not need to check if there is a valid plugin context - // Also we do not need to return the plugin info pluginInfo = null; } else diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index 3c9a387..be8d07d 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -104,7 +104,6 @@ private void Execute(SourceProductionContext spc, if (useDI) { // If we use dependency injection, we do not need to check if there is a valid plugin context - // Also we do not need to return the plugin info pluginInfo = null; } else From 1673504f58a04dcf6ea7adaf3867eb1df69eb090 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 20:05:48 +0800 Subject: [PATCH 16/23] Use one internal static api instance --- .../Constants.cs | 4 + .../Localize/EnumSourceGenerator.cs | 4 +- .../Localize/LocalizeSourceGenerator.cs | 7 +- .../Localize/PublicApiSourceGenerator.cs | 107 ++++++++++++++++++ 4 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 Flow.Launcher.Localization.SourceGenerators/Localize/PublicApiSourceGenerator.cs diff --git a/Flow.Launcher.Localization.Shared/Constants.cs b/Flow.Launcher.Localization.Shared/Constants.cs index a4610ec..dee43d0 100644 --- a/Flow.Launcher.Localization.Shared/Constants.cs +++ b/Flow.Launcher.Localization.Shared/Constants.cs @@ -24,6 +24,10 @@ public static class Constants public const string EnumLocalizeAttributeName = "EnumLocalizeAttribute"; public const string EnumLocalizeKeyAttributeName = "EnumLocalizeKeyAttribute"; public const string EnumLocalizeValueAttributeName = "EnumLocalizeValueAttribute"; + // Use PublicApi instead of PublicAPI for possible combiguity with Flow.Launcher.Plugin.IPublicAPI + public const string PublicApiClassName = "PublicApi"; + public const string PublicApiPrivatePropertyName = "instance"; + public const string PublicApiInternalPropertyName = "Instance"; public static readonly Regex LanguagesXamlRegex = new Regex(@"\\Languages\\[^\\]+\.xaml$", RegexOptions.IgnoreCase); public static readonly string[] OldLocalizationClasses = { "IPublicAPI", "Internationalization" }; diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 2b031ca..a26b688 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -227,8 +227,8 @@ private void GenerateSource( string getTranslation = null; if (useDI) { - // Use Api from LocalizeSourceGenerator - getTranslation = $"{assemblyNamespace}.{Constants.ClassName}.Api.GetTranslation"; + // Use instance from PublicApiSourceGenerator + getTranslation = $"{assemblyNamespace}.{Constants.PublicApiClassName}.{Constants.PublicApiInternalPropertyName}.GetTranslation"; } else if (pluginInfo?.IsValid == true) { diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index be8d07d..43e6ee1 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -510,11 +510,8 @@ private static void GenerateSource( string getTranslation = null; if (useDI) { - sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI? api = null;"); - // Internal for EnumSourceGenerator to use - sourceBuilder.AppendLine($"{tabString}internal static Flow.Launcher.Plugin.IPublicAPI Api => api ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService();"); - sourceBuilder.AppendLine(); - getTranslation = "Api.GetTranslation"; + // Use instance from PublicApiSourceGenerator + getTranslation = $"{assemblyNamespace}.{Constants.PublicApiClassName}.{Constants.PublicApiInternalPropertyName}.GetTranslation"; } else if (pluginInfo?.IsValid == true) { diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/PublicApiSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/PublicApiSourceGenerator.cs new file mode 100644 index 0000000..2c3d1a1 --- /dev/null +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/PublicApiSourceGenerator.cs @@ -0,0 +1,107 @@ +using System; +using System.Text; +using Flow.Launcher.Localization.Shared; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Flow.Launcher.Localization.SourceGenerators.Localize +{ + [Generator] + public partial class PublicApiSourceGenerator : IIncrementalGenerator + { + #region Fields + + private static readonly Version PackageVersion = typeof(PublicApiSourceGenerator).Assembly.GetName().Version; + + #endregion + + #region Incremental Generator + + /// + /// Initializes the generator and registers source output based on build property FLLUseDependencyInjection. + /// + /// The initialization context. + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var compilation = context.CompilationProvider; + + var configOptions = context.AnalyzerConfigOptionsProvider; + + var compilationEnums = configOptions.Combine(compilation); + + context.RegisterSourceOutput(compilationEnums, Execute); + } + + /// + /// Executes the generation of public api property based on the provided data. + /// + /// The source production context. + /// The provided data. + private void Execute(SourceProductionContext spc, + (AnalyzerConfigOptionsProvider ConfigOptionsProvider, Compilation Compilation) data) + { + var compilation = data.Compilation; + var configOptions = data.ConfigOptionsProvider; + + var assemblyNamespace = compilation.AssemblyName ?? Constants.DefaultNamespace; + var useDI = configOptions.GetFLLUseDependencyInjection(); + + // If we do not use dependency injection, we do not need to generate the public api property + if (!useDI) return; + + GenerateSource(spc, assemblyNamespace); + } + + #endregion + + #region Generate Source + + private void GenerateSource( + SourceProductionContext spc, + string assemblyNamespace) + { + var tabString = Helper.Spacing(1); + + var sourceBuilder = new StringBuilder(); + + // Generate header + GeneratedHeaderFromPath(sourceBuilder); + sourceBuilder.AppendLine(); + + // Generate nullable enable + sourceBuilder.AppendLine("#nullable enable"); + sourceBuilder.AppendLine(); + + // Generate namespace + sourceBuilder.AppendLine($"namespace {assemblyNamespace};"); + sourceBuilder.AppendLine(); + + // Generate class + sourceBuilder.AppendLine($"[System.CodeDom.Compiler.GeneratedCode(\"{nameof(PublicApiSourceGenerator)}\", \"{PackageVersion}\")]"); + sourceBuilder.AppendLine($"internal static class {Constants.PublicApiClassName}"); + sourceBuilder.AppendLine("{"); + + // Generate properties + sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI? {Constants.PublicApiPrivatePropertyName} = null;"); + sourceBuilder.AppendLine(); + sourceBuilder.AppendLine($"{tabString}/// "); + sourceBuilder.AppendLine($"{tabString}/// Get instance"); + sourceBuilder.AppendLine($"{tabString}/// "); + sourceBuilder.AppendLine($"{tabString}internal static Flow.Launcher.Plugin.IPublicAPI {Constants.PublicApiInternalPropertyName} => {Constants.PublicApiPrivatePropertyName} ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService();"); + sourceBuilder.AppendLine(); + + sourceBuilder.AppendLine($"}}"); + + // Add source to context + spc.AddSource($"{Constants.PublicApiClassName}.{assemblyNamespace}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } + + private static void GeneratedHeaderFromPath(StringBuilder sb) + { + sb.AppendLine("/// "); + } + + #endregion + } +} From 965ede1f474580517370580f3ed68b7640e8c867 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 20:09:29 +0800 Subject: [PATCH 17/23] Slight adjust --- .../Localize/PublicApiSourceGenerator.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/PublicApiSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/PublicApiSourceGenerator.cs index 2c3d1a1..92593b7 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/PublicApiSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/PublicApiSourceGenerator.cs @@ -88,9 +88,8 @@ private void GenerateSource( sourceBuilder.AppendLine($"{tabString}/// "); sourceBuilder.AppendLine($"{tabString}/// Get instance"); sourceBuilder.AppendLine($"{tabString}/// "); - sourceBuilder.AppendLine($"{tabString}internal static Flow.Launcher.Plugin.IPublicAPI {Constants.PublicApiInternalPropertyName} => {Constants.PublicApiPrivatePropertyName} ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService();"); - sourceBuilder.AppendLine(); - + sourceBuilder.AppendLine($"{tabString}internal static Flow.Launcher.Plugin.IPublicAPI {Constants.PublicApiInternalPropertyName} =>" + + $"{Constants.PublicApiPrivatePropertyName} ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService();"); sourceBuilder.AppendLine($"}}"); // Add source to context From 6ecdbb82866a454c5b88e703176dadac9e9e4400 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 20:18:29 +0800 Subject: [PATCH 18/23] Restore assembly version --- .../Flow.Launcher.Localization.SourceGenerators.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj b/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj index 90bd0d3..be6dda3 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj +++ b/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj @@ -5,7 +5,7 @@ true Flow.Launcher.Localization.SourceGenerators - 0.2.15 + 0.0.2 From c55d88d1da50845e47a2c8260120bd2f27f4b335 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 20:26:29 +0800 Subject: [PATCH 19/23] Use string.IsNullOrWhiteSpace instead of null check or IsNullOrEmpty --- .../Localize/EnumSourceGenerator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index a26b688..3f3410a 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -131,7 +131,8 @@ private static ImmutableArray GetEnumFields(SourceProductionContext s var key = keyAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty; var value = valueAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty; - if (keyAttrExist && !string.IsNullOrEmpty(key)) + // Users may use " " as a key, so we need to check if the key is not empty and not whitespace + if (keyAttrExist && !string.IsNullOrWhiteSpace(key)) { // If localization key exists and is valid, use it enumFields.Add(new EnumField(enumFieldName, key, valueAttrExist ? value : null)); @@ -316,7 +317,8 @@ private static void GenerateUpdateLabelsMethod( sb.AppendLine($"{tabString}{{"); sb.AppendLine($"{tabString}{tabString}foreach (var item in options)"); sb.AppendLine($"{tabString}{tabString}{{"); - sb.AppendLine($"{tabString}{tabString}{tabString}if (!string.IsNullOrEmpty(item.LocalizationKey))"); + // Users may use " " as a key, so we need to check if the key is not empty and not whitespace + sb.AppendLine($"{tabString}{tabString}{tabString}if (!string.IsNullOrWhiteSpace(item.LocalizationKey))"); sb.AppendLine($"{tabString}{tabString}{tabString}{{"); sb.AppendLine($"{tabString}{tabString}{tabString}{tabString}item.Display = {getTranslation}(item.LocalizationKey);"); sb.AppendLine($"{tabString}{tabString}{tabString}}}"); @@ -334,7 +336,8 @@ public class EnumField public string LocalizationKey { get; set; } public string LocalizationValue { get; set; } - public bool UseLocalizationKey => LocalizationKey != null; + // Users may use " " as a key, so we need to check if the key is not empty and not whitespace + public bool UseLocalizationKey => !string.IsNullOrWhiteSpace(LocalizationKey); public EnumField(string enumFieldName, string localizationValue) : this(enumFieldName, null, localizationValue) { From 23cf8f11901ab41c8bb365980b8a50db30ac9056 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 09:32:28 +0800 Subject: [PATCH 20/23] Improve tab string performance --- Flow.Launcher.Localization.Shared/Helper.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Flow.Launcher.Localization.Shared/Helper.cs b/Flow.Launcher.Localization.Shared/Helper.cs index 044fa45..0807cef 100644 --- a/Flow.Launcher.Localization.Shared/Helper.cs +++ b/Flow.Launcher.Localization.Shared/Helper.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text; +using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -88,16 +86,7 @@ private static Location GetCodeFixLocation(PropertyDeclarationSyntax property, S public static string Spacing(int n) { - Span spaces = stackalloc char[n * 4]; - spaces.Fill(' '); - - var sb = new StringBuilder(n * 4); - foreach (var c in spaces) - { - _ = sb.Append(c); - } - - return sb.ToString(); + return new string(' ', n * 4); } #endregion From 903f22ec9836929eda44e61dad7d2267a49c172b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 09:36:03 +0800 Subject: [PATCH 21/23] Remove \n for last string --- .../Localize/LocalizeSourceGenerator.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index 43e6ee1..43589ce 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -521,8 +521,9 @@ private static void GenerateSource( // Generate localization methods foreach (var ls in localizedStrings) { + var isLast = ls.Equals(localizedStrings.Last()); GenerateDocComments(sourceBuilder, ls, tabString); - GenerateLocalizationMethod(sourceBuilder, ls, getTranslation, tabString); + GenerateLocalizationMethod(sourceBuilder, ls, getTranslation, tabString, isLast); } sourceBuilder.AppendLine("}"); @@ -579,7 +580,8 @@ private static void GenerateLocalizationMethod( StringBuilder sb, LocalizableString ls, string getTranslation, - string tabString) + string tabString, + bool last) { sb.Append($"{tabString}public static string {ls.Key}("); @@ -604,7 +606,10 @@ private static void GenerateLocalizationMethod( sb.AppendLine("\"LOCALIZATION_ERROR\";"); } - sb.AppendLine(); + if (!last) + { + sb.AppendLine(); + } } #endregion From d7a8a2bfc986f7498bb4ee79b13a0a5fe5f4c767 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 09:39:39 +0800 Subject: [PATCH 22/23] Adjust enum full name style --- .../Localize/EnumSourceGenerator.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs index 3f3410a..76a218f 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/EnumSourceGenerator.cs @@ -171,7 +171,9 @@ private void GenerateSource( PluginClassInfo pluginInfo, string assemblyNamespace) { - var enumFullName = enumSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var enumFullName = enumSymbol.ToDisplayString(new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, // Remove global:: symbol + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)); var enumDataClassName = $"{enumSymbol.Name}{Constants.EnumLocalizeClassSuffix}"; var enumName = enumSymbol.Name; var enumNamespace = enumSymbol.ContainingNamespace.ToDisplayString(); From 169a4b5b1a6830759e7682cfd3c88de3dfe50a7c Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Fri, 4 Apr 2025 09:48:36 +0800 Subject: [PATCH 23/23] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Localization.Shared/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Localization.Shared/Constants.cs b/Flow.Launcher.Localization.Shared/Constants.cs index dee43d0..8932aa9 100644 --- a/Flow.Launcher.Localization.Shared/Constants.cs +++ b/Flow.Launcher.Localization.Shared/Constants.cs @@ -24,7 +24,7 @@ public static class Constants public const string EnumLocalizeAttributeName = "EnumLocalizeAttribute"; public const string EnumLocalizeKeyAttributeName = "EnumLocalizeKeyAttribute"; public const string EnumLocalizeValueAttributeName = "EnumLocalizeValueAttribute"; - // Use PublicApi instead of PublicAPI for possible combiguity with Flow.Launcher.Plugin.IPublicAPI + // Use PublicApi instead of PublicAPI for possible ambiguity with Flow.Launcher.Plugin.IPublicAPI public const string PublicApiClassName = "PublicApi"; public const string PublicApiPrivatePropertyName = "instance"; public const string PublicApiInternalPropertyName = "Instance";