From e726e390acd5f786a053656c5960233437ed79b4 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Tue, 27 Sep 2022 22:48:48 -0400 Subject: [PATCH] feat: Add support for attached properties localization --- .../XamlGenerator/XamlCodeGeneration.cs | 21 +++++++++- .../XamlFileGenerator.Reflection.cs | 38 +++++++++++++++++ .../XamlGenerator/XamlFileGenerator.cs | 41 +++++++++++++++---- .../UnoPRIResourcesWriter.cs | 21 +++++++++- .../Strings/en/Resources.Designer.cs | 9 ++++ .../ResourceLoader/Strings/en/Resources.resw | 11 +++-- src/Uno.UI.Tests/Uno.UI.Tests.csproj | 8 +++- .../When_XUid_And_AttachedProperty.xaml | 20 +++++++++ .../When_XUid_And_AttachedProperty.xaml.cs | 25 +++++++++++ .../XUidTests/Given_xUid.cs | 9 ++++ .../Resources/ResourceLoader.cs | 30 +++++++++++++- 11 files changed, 213 insertions(+), 20 deletions(-) create mode 100644 src/Uno.UI.Tests/Windows_UI_Xaml_Markup/XUidTests/Controls/When_XUid_And_AttachedProperty.xaml create mode 100644 src/Uno.UI.Tests/Windows_UI_Xaml_Markup/XUidTests/Controls/When_XUid_And_AttachedProperty.xaml.cs diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs index 43a26cf55b8d..f280c2f965b5 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs @@ -613,12 +613,14 @@ private string[] GetResourceKeys(CancellationToken ct) var doc = new XmlDocument(); doc.LoadXml(sourceText.ToString()); + var rewriterBuilder = new StringBuilder(); + //extract all localization keys from Win10 resource file // https://docs.microsoft.com/en-us/dotnet/standard/data/xml/compiled-xpath-expressions?redirectedfrom=MSDN#higher-performance-xpath-expressions // Per this documentation, /root/data should be more performant than //data var keys = doc.SelectNodes("/root/data") ?.Cast() - .Select(node => node.GetAttribute("name")) + .Select(node => RewriteResourceKeyName(rewriterBuilder, node.GetAttribute("name"))) .ToArray() ?? Array.Empty(); _cachedResources[cachedFileKey] = new CachedResource(DateTimeOffset.Now, keys); return keys; @@ -642,7 +644,6 @@ private string[] GetResourceKeys(CancellationToken ct) } }) .Distinct() - .Select(k => k.Replace('.', '/')) .ToArray(); #if DEBUG @@ -651,6 +652,22 @@ private string[] GetResourceKeys(CancellationToken ct) return resourceKeys; } + private string RewriteResourceKeyName(StringBuilder builder, string keyName) + { + var firstDotIndex = keyName.IndexOf('.'); + if (firstDotIndex != -1) + { + builder.Clear(); + builder.Append(keyName); + + builder[firstDotIndex] = '/'; + + return builder.ToString(); + } + + return keyName; + } + private DateTime GetLastBinaryUpdateTime() { // Determine the last update time, to allow for the re-generation of the files. diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.Reflection.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.Reflection.cs index 315ee8e20fbf..8b399a4588ab 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.Reflection.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.Reflection.cs @@ -899,6 +899,44 @@ private IEnumerable FindLocalizableProperties(XamlType xamlType) type = type.BaseType; } } + private bool IsAttachedProperty(INamedTypeSymbol declaringType, string name) + => _isAttachedProperty(declaringType, name); + + private IEnumerable<(INamedTypeSymbol ownerType, string property)> FindLocalizableAttachedProperties(string uid) + { + foreach (var key in _resourceKeys.Where(k => k.StartsWith(uid + "/"))) + { + // fullKey = $"{uidName}.[using:{ns}]{type}.{memberName}"; + // + // Example: + // OpenVideosButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip + + var firstDotIndex = key.IndexOf('/'); + + var propertyPath = key.Substring(firstDotIndex + 1); + + const string usingPattern = "[using:"; + + if(propertyPath.StartsWith(usingPattern)) + { + var lastDotIndex = propertyPath.LastIndexOf('.'); + + var propertyName = propertyPath.Substring(lastDotIndex + 1); + var typeName = propertyPath + .Substring(usingPattern.Length, lastDotIndex - usingPattern.Length) + .Replace("]", "."); + + if(GetType(typeName) is { } typeSymbol) + { + yield return (typeSymbol, propertyName); + } + else + { + throw new Exception($"Unable to find the type {typeName} in key {key}"); + } + } + } + } private string[] FindLocalizableDeclaredProperties(INamedTypeSymbol type) => _findLocalizableDeclaredProperties!(type); diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs index 4130ef499638..2017484adbc4 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs @@ -3135,7 +3135,7 @@ private void BuildExtendedProperties(IIndentedStringBuilder outerwriter, XamlObj var isFrameworkElement = IsType(objectDefinitionType, _frameworkElementSymbol); var hasIsParsing = HasIsParsing(objectDefinitionType); - if (extendedProperties.Any() || hasChildrenWithPhase || isFrameworkElement || hasIsParsing) + if (extendedProperties.Any() || hasChildrenWithPhase || isFrameworkElement || hasIsParsing || objectUid.HasValue()) { string closureName; if (!useGenericApply && objectDefinitionType is null) @@ -3552,6 +3552,8 @@ private void BuildExtendedProperties(IIndentedStringBuilder outerwriter, XamlObj BuildUiAutomationId(writer, closureName, uiAutomationId, objectDefinition); } + BuildStatementLocalizedProperties(writer, objectDefinition, closureName); + if (hasIsParsing // If true then this apply block will be applied to the content of a UserControl, which will already have had CreationComplete() called in its own apply block. && !useChildTypeForNamedElement @@ -3807,7 +3809,7 @@ IMethodSymbol FindTargetMethodSymbol(INamedTypeSymbol? sourceType) /// /// Build localized properties which have not been set in the xaml. /// - private void BuildLocalizedProperties(IIndentedStringBuilder writer, XamlObjectDefinition objectDefinition) + private void BuildInlineLocalizedProperties(IIndentedStringBuilder writer, XamlObjectDefinition objectDefinition) { TryAnnotateWithGeneratorSource(writer); var objectUid = GetObjectUid(objectDefinition); @@ -3827,6 +3829,28 @@ private void BuildLocalizedProperties(IIndentedStringBuilder writer, XamlObjectD } } + /// + /// Build localized properties which have not been set in the xaml. + /// + private void BuildStatementLocalizedProperties(IIndentedStringBuilder writer, XamlObjectDefinition objectDefinition, string closureName) + { + TryAnnotateWithGeneratorSource(writer); + var objectUid = GetObjectUid(objectDefinition); + + if (objectUid != null) + { + var candidateAttachedProperties = FindLocalizableAttachedProperties(objectUid); + foreach (var candidate in candidateAttachedProperties) + { + var localizedValue = BuildLocalizedResourceValue(candidate.ownerType, candidate.property, objectUid); + if (localizedValue != null) + { + writer.AppendLineInvariantIndented($"{candidate.ownerType}.Set{candidate.property}({closureName}, {localizedValue});"); + } + } + } + } + private void TryValidateContentPresenterBinding(IIndentedStringBuilder writer, XamlObjectDefinition objectDefinition, XamlMemberDefinition member) { TryAnnotateWithGeneratorSource(writer); @@ -4637,7 +4661,7 @@ string Inner() { if (IsLocalizedString(propertyType, objectUid)) { - var resourceValue = BuildLocalizedResourceValue(owner, memberName, objectUid); + var resourceValue = BuildLocalizedResourceValue(FindType(owner?.Member.DeclaringType), memberName, objectUid); if (resourceValue != null) { @@ -4855,7 +4879,7 @@ string Inner() } } - private string? BuildLocalizedResourceValue(XamlMemberDefinition? owner, string memberName, string objectUid) + private string? BuildLocalizedResourceValue(INamedTypeSymbol? owner, string memberName, string objectUid) { // see: https://docs.microsoft.com/en-us/windows/uwp/app-resources/localize-strings-ui-manifest // Valid formats: @@ -4888,13 +4912,12 @@ string Inner() //windows 10 localization concat the xUid Value with the member value (Text, Content, Header etc...) var fullKey = uidName + "/" + memberName; - if (owner != null && IsAttachedProperty(owner)) + if (owner != null && IsAttachedProperty(owner, memberName)) { - var declaringType = GetType(owner.Member.DeclaringType); + var declaringType = owner; var nsRaw = declaringType.ContainingNamespace.GetFullName(); - var ns = nsRaw?.Replace(".", "/"); var type = declaringType.Name; - fullKey = $"{uidName}/[using:{ns}]{type}/{memberName}"; + fullKey = $"{uidName}/[using:{nsRaw}]{type}.{memberName}"; } if (_resourceKeys.Any(k => k == fullKey)) @@ -5790,7 +5813,7 @@ private void BuildChild(IIndentedStringBuilder writer, XamlMemberDefinition? own RegisterAndBuildResources(writer, xamlObjectDefinition, isInInitializer: true); BuildLiteralProperties(writer, xamlObjectDefinition); BuildProperties(writer, xamlObjectDefinition); - BuildLocalizedProperties(writer, xamlObjectDefinition); + BuildInlineLocalizedProperties(writer, xamlObjectDefinition); } BuildExtendedProperties(writer, xamlObjectDefinition); diff --git a/src/SourceGenerators/Uno.UI.Tasks/ResourcesGenerator/UnoPRIResourcesWriter.cs b/src/SourceGenerators/Uno.UI.Tasks/ResourcesGenerator/UnoPRIResourcesWriter.cs index 86e55d69c3dd..3359a53dcd1d 100644 --- a/src/SourceGenerators/Uno.UI.Tasks/ResourcesGenerator/UnoPRIResourcesWriter.cs +++ b/src/SourceGenerators/Uno.UI.Tasks/ResourcesGenerator/UnoPRIResourcesWriter.cs @@ -16,15 +16,32 @@ internal static void Write(string resourceMapName, string language, Dictionary + /// Looks up a localized string similar to Localized value. + /// + internal static string OpenVideosButton__using_Windows_UI_Xaml_Controls_ToolTipService_ToolTip { + get { + return ResourceManager.GetString("OpenVideosButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to . /// diff --git a/src/Uno.UI.Tests/ResourceLoader/Strings/en/Resources.resw b/src/Uno.UI.Tests/ResourceLoader/Strings/en/Resources.resw index 2c61a1e0a5fd..5da61766eff3 100644 --- a/src/Uno.UI.Tests/ResourceLoader/Strings/en/Resources.resw +++ b/src/Uno.UI.Tests/ResourceLoader/Strings/en/Resources.resw @@ -117,13 +117,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + App70-en - + Text in 'en' - + Header in 'en' @@ -135,4 +135,7 @@ - + + Localized value + + \ No newline at end of file diff --git a/src/Uno.UI.Tests/Uno.UI.Tests.csproj b/src/Uno.UI.Tests/Uno.UI.Tests.csproj index 36623242db98..e4bd7dcfc64d 100644 --- a/src/Uno.UI.Tests/Uno.UI.Tests.csproj +++ b/src/Uno.UI.Tests/Uno.UI.Tests.csproj @@ -10,7 +10,13 @@ true true - + true full diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Markup/XUidTests/Controls/When_XUid_And_AttachedProperty.xaml b/src/Uno.UI.Tests/Windows_UI_Xaml_Markup/XUidTests/Controls/When_XUid_And_AttachedProperty.xaml new file mode 100644 index 000000000000..799dbe8e6deb --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Markup/XUidTests/Controls/When_XUid_And_AttachedProperty.xaml @@ -0,0 +1,20 @@ + + + +