diff --git a/Flow.Launcher.Localization.Analyzers/Flow.Launcher.Localization.Analyzers.csproj b/Flow.Launcher.Localization.Analyzers/Flow.Launcher.Localization.Analyzers.csproj index ce219d0..523f664 100644 --- a/Flow.Launcher.Localization.Analyzers/Flow.Launcher.Localization.Analyzers.csproj +++ b/Flow.Launcher.Localization.Analyzers/Flow.Launcher.Localization.Analyzers.csproj @@ -16,4 +16,8 @@ + + + + diff --git a/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs b/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs index 8123d25..72a2775 100644 --- a/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs +++ b/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Linq; +using Flow.Launcher.Localization.Shared; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -17,10 +18,6 @@ public class ContextAvailabilityAnalyzer : DiagnosticAnalyzer AnalyzerDiagnostics.ContextIsNotDeclared ); - private const string PluginContextTypeName = "PluginInitContext"; - - private const string PluginInterfaceName = "IPluginI18n"; - public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); @@ -30,6 +27,12 @@ public override void Initialize(AnalysisContext context) private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { + var configOptions = context.Options.AnalyzerConfigOptionsProvider; + var useDI = configOptions.GetFLLUseDependencyInjection(); + + // If we use dependency injection, we don't need to check for this context property + if (useDI) return; + var classDeclaration = (ClassDeclarationSyntax)context.Node; var semanticModel = context.SemanticModel; var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration); @@ -38,7 +41,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) var contextProperty = classDeclaration.Members.OfType() .Select(p => semanticModel.GetDeclaredSymbol(p)) - .FirstOrDefault(p => p?.Type.Name is PluginContextTypeName); + .FirstOrDefault(p => p?.Type.Name is Constants.PluginContextTypeName); if (contextProperty != null) { @@ -67,7 +70,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) .OfType() .SelectMany(f => f.Declaration.Variables) .Select(f => semanticModel.GetDeclaredSymbol(f)) - .FirstOrDefault(f => f is IFieldSymbol fs && fs.Type.Name is PluginContextTypeName); + .FirstOrDefault(f => f is IFieldSymbol fs && fs.Type.Name is Constants.PluginContextTypeName); var parentSyntax = fieldDeclaration ?.DeclaringSyntaxReferences[0] .GetSyntax() @@ -89,6 +92,6 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) } private static bool IsPluginEntryClass(INamedTypeSymbol namedTypeSymbol) => - namedTypeSymbol?.Interfaces.Any(i => i.Name == PluginInterfaceName) ?? false; + namedTypeSymbol?.Interfaces.Any(i => i.Name == Constants.PluginInterfaceName) ?? false; } } diff --git a/Flow.Launcher.Localization.Analyzers/Localize/OldGetTranslateAnalyzerCodeFixProvider.cs b/Flow.Launcher.Localization.Analyzers/Localize/OldGetTranslateAnalyzerCodeFixProvider.cs index 37552ce..1040270 100644 --- a/Flow.Launcher.Localization.Analyzers/Localize/OldGetTranslateAnalyzerCodeFixProvider.cs +++ b/Flow.Launcher.Localization.Analyzers/Localize/OldGetTranslateAnalyzerCodeFixProvider.cs @@ -109,6 +109,5 @@ InvocationExpressionSyntax invocationExpr var newRoot = root.ReplaceNode(invocationExpr, newInnerInvocationExpr); return context.Document.WithSyntaxRoot(newRoot); } - } } diff --git a/Flow.Launcher.Localization.Shared/Constants.cs b/Flow.Launcher.Localization.Shared/Constants.cs new file mode 100644 index 0000000..beac8aa --- /dev/null +++ b/Flow.Launcher.Localization.Shared/Constants.cs @@ -0,0 +1,18 @@ +using System.Text.RegularExpressions; + +namespace Flow.Launcher.Localization.Shared +{ + public static class Constants + { + public const string DefaultNamespace = "Flow.Launcher"; + public const string ClassName = "Localize"; + public const string PluginInterfaceName = "IPluginI18n"; + public const string PluginContextTypeName = "PluginInitContext"; + public const string SystemPrefixUri = "clr-namespace:System;assembly=mscorlib"; + public const string XamlPrefixUri = "http://schemas.microsoft.com/winfx/2006/xaml"; + public const string XamlTag = "String"; + public const string KeyAttribute = "Key"; + + public static readonly Regex LanguagesXamlRegex = new Regex(@"\\Languages\\[^\\]+\.xaml$", RegexOptions.IgnoreCase); + } +} diff --git a/Flow.Launcher.Localization.Shared/Flow.Launcher.Localization.Shared.csproj b/Flow.Launcher.Localization.Shared/Flow.Launcher.Localization.Shared.csproj new file mode 100644 index 0000000..ae15976 --- /dev/null +++ b/Flow.Launcher.Localization.Shared/Flow.Launcher.Localization.Shared.csproj @@ -0,0 +1,14 @@ + + + + 0.0.1 + netstandard2.0 + true + Flow.Launcher.Localization.Shared + + + + + + + diff --git a/Flow.Launcher.Localization.Shared/Helper.cs b/Flow.Launcher.Localization.Shared/Helper.cs new file mode 100644 index 0000000..94b186c --- /dev/null +++ b/Flow.Launcher.Localization.Shared/Helper.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Flow.Launcher.Localization.Shared +{ + public static class Helper + { + public static bool GetFLLUseDependencyInjection(this AnalyzerConfigOptionsProvider configOptions) + { + if (!configOptions.GlobalOptions.TryGetValue("build_property.FLLUseDependencyInjection", out var result) || + !bool.TryParse(result, out var useDI)) + { + return false; // Default to false + } + return useDI; + } + } +} diff --git a/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj b/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj index 40d585d..db8fa41 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj +++ b/Flow.Launcher.Localization.SourceGenerators/Flow.Launcher.Localization.SourceGenerators.csproj @@ -14,5 +14,9 @@ + + + + diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index 9a52e3e..583ea8d 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -3,12 +3,13 @@ using System.Collections.Immutable; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Xml.Linq; +using Flow.Launcher.Localization.Shared; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; namespace Flow.Launcher.Localization.SourceGenerators.Localize @@ -21,19 +22,6 @@ public partial class LocalizeSourceGenerator : IIncrementalGenerator { #region Fields - private const string CoreNamespace1 = "Flow.Launcher"; - private const string CoreNamespace2 = "Flow.Launcher.Core"; - private const string DefaultNamespace = "Flow.Launcher"; - private const string ClassName = "Localize"; - private const string PluginInterfaceName = "IPluginI18n"; - private const string PluginContextTypeName = "PluginInitContext"; - private const string systemPrefixUri = "clr-namespace:System;assembly=mscorlib"; - private const string xamlPrefixUri = "http://schemas.microsoft.com/winfx/2006/xaml"; - private const string XamlTag = "String"; - private const string KeyTag = "Key"; - - private static readonly Regex _languagesXamlRegex = new Regex(@"\\Languages\\[^\\]+\.xaml$", RegexOptions.IgnoreCase); - private static readonly Version PackageVersion = typeof(LocalizeSourceGenerator).Assembly.GetName().Version; private static readonly ImmutableArray _emptyLocalizableStrings = ImmutableArray.Empty; @@ -50,7 +38,7 @@ public partial class LocalizeSourceGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { var xamlFiles = context.AdditionalTextsProvider - .Where(file => _languagesXamlRegex.IsMatch(file.Path)); + .Where(file => Constants.LanguagesXamlRegex.IsMatch(file.Path)); var localizedStrings = xamlFiles .Select((file, ct) => ParseXamlFile(file, ct)) @@ -75,7 +63,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var compilation = context.CompilationProvider; - var combined = localizedStrings.Combine(invocationKeys).Combine(pluginClasses).Combine(compilation).Combine(xamlFiles.Collect()); + var configOptions = context.AnalyzerConfigOptionsProvider; + + var combined = localizedStrings.Combine(invocationKeys).Combine(pluginClasses).Combine(configOptions).Combine(compilation).Combine(xamlFiles.Collect()); context.RegisterSourceOutput(combined, Execute); } @@ -86,10 +76,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) /// The source production context. /// The provided data. private void Execute(SourceProductionContext spc, - ((((ImmutableArray LocalizableStrings, - ImmutableHashSet InvocationKeys), - ImmutableArray PluginClassInfos), - Compilation Compilation), + (((((ImmutableArray LocalizableStrings, + ImmutableHashSet InvocationKeys), + ImmutableArray PluginClassInfos), + AnalyzerConfigOptionsProvider ConfigOptionsProvider), + Compilation Compilation), ImmutableArray AdditionalTexts) data) { var xamlFiles = data.AdditionalTexts; @@ -103,23 +94,28 @@ private void Execute(SourceProductionContext spc, } var compilation = data.Item1.Compilation; - var pluginClasses = data.Item1.Item1.PluginClassInfos; - var usedKeys = data.Item1.Item1.Item1.InvocationKeys; - var localizedStrings = data.Item1.Item1.Item1.LocalizableStrings; + var configOptions = data.Item1.Item1.ConfigOptionsProvider; + var pluginClasses = data.Item1.Item1.Item1.PluginClassInfos; + var usedKeys = data.Item1.Item1.Item1.Item1.InvocationKeys; + var localizedStrings = data.Item1.Item1.Item1.Item1.LocalizableStrings; - var assemblyName = compilation.AssemblyName ?? DefaultNamespace; + var assemblyName = compilation.AssemblyName ?? Constants.DefaultNamespace; var optimizationLevel = compilation.Options.OptimizationLevel; + var useDI = configOptions.GetFLLUseDependencyInjection(); - var pluginInfo = GetValidPluginInfo(pluginClasses, spc); - var isCoreAssembly = assemblyName == CoreNamespace1 || assemblyName == CoreNamespace2; - + PluginClassInfo pluginInfo = null; + if (!useDI) + { + pluginInfo = GetValidPluginInfo(pluginClasses, spc); + } + GenerateSource( spc, xamlFiles[0], localizedStrings, optimizationLevel, assemblyName, - isCoreAssembly, + useDI, pluginInfo, usedKeys); } @@ -155,11 +151,11 @@ private static ImmutableArray ParseXamlFile(AdditionalText fi string uri = attr.Value; string prefix = attr.Name.LocalName; - if (uri == systemPrefixUri) + if (uri == Constants.SystemPrefixUri) { systemPrefix = prefix; } - else if (uri == xamlPrefixUri) + else if (uri == Constants.XamlPrefixUri) { xamlPrefix = prefix; } @@ -179,14 +175,14 @@ private static ImmutableArray ParseXamlFile(AdditionalText fi } var localizableStrings = new List(); - foreach (var element in doc.Descendants(systemNs + XamlTag)) // "String" elements in system namespace + foreach (var element in doc.Descendants(systemNs + Constants.XamlTag)) // "String" elements in system namespace { if (ct.IsCancellationRequested) { return _emptyLocalizableStrings; } - var key = element.Attribute(xNs + KeyTag)?.Value; // "Key" attribute in xaml namespace + var key = element.Attribute(xNs + Constants.KeyAttribute)?.Value; // "Key" attribute in xaml namespace var value = element.Value; var comment = element.PreviousNode as XComment; @@ -424,7 +420,7 @@ private static string GetLocalizationKeyFromInvocation(GeneratorSyntaxContext co parts.Reverse(); // Check if the first part is ClassName and there's at least one more part - if (parts.Count < 2 || parts[0] != ClassName) + if (parts.Count < 2 || parts[0] != Constants.ClassName) { return null; } @@ -440,7 +436,7 @@ private static PluginClassInfo GetPluginClassInfo(GeneratorSyntaxContext context { var classDecl = (ClassDeclarationSyntax)context.Node; var location = GetLocation(context.SemanticModel.SyntaxTree, classDecl); - if (!classDecl.BaseList?.Types.Any(t => t.Type.ToString() == PluginInterfaceName) ?? true) + if (!classDecl.BaseList?.Types.Any(t => t.Type.ToString() == Constants.PluginInterfaceName) ?? true) { // Cannot find class that implements IPluginI18n return null; @@ -448,7 +444,7 @@ private static PluginClassInfo GetPluginClassInfo(GeneratorSyntaxContext context var property = classDecl.Members .OfType() - .FirstOrDefault(p => p.Type.ToString() == PluginContextTypeName); + .FirstOrDefault(p => p.Type.ToString() == Constants.PluginContextTypeName); if (property is null) { // Cannot find context @@ -532,7 +528,7 @@ private static void GenerateSource( ImmutableArray localizedStrings, OptimizationLevel optimizationLevel, string assemblyName, - bool isCoreAssembly, + bool useDI, PluginClassInfo pluginInfo, IEnumerable usedKeys) { @@ -560,13 +556,6 @@ private static void GenerateSource( GeneratedHeaderFromPath(sourceBuilder, xamlFile.Path); sourceBuilder.AppendLine(); - // Generate usings - if (isCoreAssembly) - { - sourceBuilder.AppendLine("using Flow.Launcher.Core.Resource;"); - sourceBuilder.AppendLine(); - } - // Generate nullable enable sourceBuilder.AppendLine("#nullable enable"); sourceBuilder.AppendLine(); @@ -603,11 +592,26 @@ private static void GenerateSource( // Generate class sourceBuilder.AppendLine($"[System.CodeDom.Compiler.GeneratedCode(\"{nameof(LocalizeSourceGenerator)}\", \"{PackageVersion}\")]"); - sourceBuilder.AppendLine($"public static class {ClassName}"); + sourceBuilder.AppendLine($"public static class {Constants.ClassName}"); sourceBuilder.AppendLine("{"); - // Generate localization methods var tabString = Spacing(1); + + // 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 localization methods foreach (var ls in localizedStrings) { // TODO: Add support for usedKeys @@ -617,13 +621,13 @@ private static void GenerateSource( }*/ GenerateDocComments(sourceBuilder, ls, tabString); - GenerateLocalizationMethod(sourceBuilder, ls, isCoreAssembly, pluginInfo, tabString); + GenerateLocalizationMethod(sourceBuilder, ls, getTranslation, tabString); } sourceBuilder.AppendLine("}"); // Add source to context - spc.AddSource($"{ClassName}.{assemblyName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + spc.AddSource($"{Constants.ClassName}.{assemblyName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } private static void GeneratedHeaderFromPath(StringBuilder sb, string xamlFilePath) @@ -673,8 +677,7 @@ private static void GenerateDocComments(StringBuilder sb, LocalizableString ls, private static void GenerateLocalizationMethod( StringBuilder sb, LocalizableString ls, - bool isCoreAssembly, - PluginClassInfo pluginInfo, + string getTranslation, string tabString) { sb.Append($"{tabString}public static string {ls.Key}("); @@ -687,18 +690,8 @@ private static void GenerateLocalizationMethod( ? $", {string.Join(", ", parameters.Select(p => p.Name))}" : string.Empty; - if (isCoreAssembly) - { - var getTranslation = "InternationalizationManager.Instance.GetTranslation"; - sb.AppendLine(parameters.Count > 0 - ? !ls.Format ? - $"string.Format({getTranslation}(\"{ls.Key}\"){formatArgs});" - : $"string.Format(System.Globalization.CultureInfo.CurrentCulture, {getTranslation}(\"{ls.Key}\"){formatArgs});" - : $"{getTranslation}(\"{ls.Key}\");"); - } - else if (pluginInfo?.IsValid == true) + if (!(string.IsNullOrEmpty(getTranslation))) { - var getTranslation = $"{pluginInfo.ContextAccessor}.API.GetTranslation"; sb.AppendLine(parameters.Count > 0 ? !ls.Format ? $"string.Format({getTranslation}(\"{ls.Key}\"){formatArgs});" diff --git a/Flow.Launcher.Localization.slnx b/Flow.Launcher.Localization.slnx index dc9bd15..27c8f44 100644 --- a/Flow.Launcher.Localization.slnx +++ b/Flow.Launcher.Localization.slnx @@ -1,5 +1,6 @@ + diff --git a/Flow.Launcher.Localization/Flow.Launcher.Localization.csproj b/Flow.Launcher.Localization/Flow.Launcher.Localization.csproj index e2f86be..03db6d9 100644 --- a/Flow.Launcher.Localization/Flow.Launcher.Localization.csproj +++ b/Flow.Launcher.Localization/Flow.Launcher.Localization.csproj @@ -1,41 +1,46 @@  - - 0.0.1 - netstandard2.0 - true - Flow.Launcher.Localization + + 0.0.1 + netstandard2.0 + true + Flow.Launcher.Localization - false - true - true - true - + false + true + true + true + - - - - + + + All + + + All + + + All + + - - - - + + + true + analyzers/dotnet/cs + false + + + true + analyzers/dotnet/cs + false + + + true + analyzers/dotnet/cs + false + + + diff --git a/Flow.Launcher.Localization/build/Flow.Launcher.Localization.props b/Flow.Launcher.Localization/build/Flow.Launcher.Localization.props new file mode 100644 index 0000000..80b3641 --- /dev/null +++ b/Flow.Launcher.Localization/build/Flow.Launcher.Localization.props @@ -0,0 +1,6 @@ + + + + + +