diff --git a/WinUI3Localizer.SampleApp/App.xaml.cs b/WinUI3Localizer.SampleApp/App.xaml.cs index ca0807d..c8a5550 100644 --- a/WinUI3Localizer.SampleApp/App.xaml.cs +++ b/WinUI3Localizer.SampleApp/App.xaml.cs @@ -133,7 +133,9 @@ private async Task InitializeLocalizer() #endif ILocalizer localizer = await new LocalizerBuilder() - .AddStringResourcesFolderForLanguageDictionaries(StringsFolderPath) + .AddPriResourcesForLanguageDictionaries(new[] { "en-US", "es-ES", "ja" } ) + .AddPriResourcesForLanguageDictionaries(new[] { "en-US", "es-ES", "ja" }, "ErrorMessages") + //.AddStringResourcesFolderForLanguageDictionaries(StringsFolderPath) //.SetLogger(Host.Services // .GetRequiredService() // .CreateLogger()) diff --git a/WinUI3Localizer.Tests/PriResourceReaderTest.cs b/WinUI3Localizer.Tests/PriResourceReaderTest.cs new file mode 100644 index 0000000..5578c75 --- /dev/null +++ b/WinUI3Localizer.Tests/PriResourceReaderTest.cs @@ -0,0 +1,52 @@ +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WinUI3Localizer.Tests; + +public class PriResourceReaderTest +{ + [Fact] + public void GetItems_ReturnsAllItems() + { + LanguageDictionary.Item[]? resourcesItems = null; + LanguageDictionary.Item[]? errorMessagesItems = null; + + Thread? thread = new Thread(() => + { + Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap.Initialize(0x00010005); + + string? priFile = Path.Combine(AppContext.BaseDirectory, "resources.pri"); + PriResourceReaderFactory? factory = new(); + + PriResourceReader? reader = factory.GetPriResourceReader(priFile); + + resourcesItems = reader.GetItems("en-US").ToArray(); + errorMessagesItems = reader.GetItems("es-ES", "ErrorMessages").ToArray(); + + Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap.Shutdown(); + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + + LanguageDictionary.Item item1 = new LanguageDictionary.Item("ControlsPage_Button", "ContentProperty", "Click", "ControlsPage_Button.Content"); + LanguageDictionary.Item item2 = new LanguageDictionary.Item("StylesPage_Top", "TextProperty", "Top", "StylesPage_Top.Text"); + LanguageDictionary.Item item3 = new LanguageDictionary.Item("/ErrorMessages/ErrorMessageExample", "TextProperty", "Ejemplo de mensajes de error", "/ErrorMessages/ErrorMessageExample.Text"); + + resourcesItems.Should().HaveCount(55); + resourcesItems.Should().Contain(item1); + resourcesItems.Should().Contain(item2); + resourcesItems.Should().NotContain(item3); + + errorMessagesItems.Should().HaveCount(1); + errorMessagesItems.Should().NotContain(item1); + errorMessagesItems.Should().NotContain(item2); + errorMessagesItems.Should().Contain(item3); + } + +} diff --git a/WinUI3Localizer.Tests/WinUI3Localizer.Tests.csproj b/WinUI3Localizer.Tests/WinUI3Localizer.Tests.csproj index 9b0d0f4..f81b1e0 100644 --- a/WinUI3Localizer.Tests/WinUI3Localizer.Tests.csproj +++ b/WinUI3Localizer.Tests/WinUI3Localizer.Tests.csproj @@ -5,11 +5,14 @@ enable enable false + x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 + @@ -30,4 +33,10 @@ + + + PreserveNewest + + + diff --git a/WinUI3Localizer.Tests/resources.pri b/WinUI3Localizer.Tests/resources.pri new file mode 100644 index 0000000..5cfcaff Binary files /dev/null and b/WinUI3Localizer.Tests/resources.pri differ diff --git a/WinUI3Localizer.sln b/WinUI3Localizer.sln index 7d0fafb..a4af6de 100644 --- a/WinUI3Localizer.sln +++ b/WinUI3Localizer.sln @@ -46,20 +46,20 @@ Global {C1E60FCC-8B43-4190-ADF1-A28B954F8DEB}.Release|x64.Build.0 = Release|Any CPU {C1E60FCC-8B43-4190-ADF1-A28B954F8DEB}.Release|x86.ActiveCfg = Release|Any CPU {C1E60FCC-8B43-4190-ADF1-A28B954F8DEB}.Release|x86.Build.0 = Release|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|ARM64.Build.0 = Debug|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x64.ActiveCfg = Debug|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x64.Build.0 = Debug|Any CPU + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|Any CPU.ActiveCfg = Debug|x64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|Any CPU.Build.0 = Debug|x64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|ARM64.ActiveCfg = Debug|arm64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|ARM64.Build.0 = Debug|arm64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x64.ActiveCfg = Debug|x64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x64.Build.0 = Debug|x64 {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x86.ActiveCfg = Debug|Any CPU {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x86.Build.0 = Debug|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|Any CPU.Build.0 = Release|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|ARM64.ActiveCfg = Release|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|ARM64.Build.0 = Release|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x64.ActiveCfg = Release|Any CPU - {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x64.Build.0 = Release|Any CPU + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|Any CPU.ActiveCfg = Release|x64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|Any CPU.Build.0 = Release|x64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|ARM64.ActiveCfg = Release|arm64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|ARM64.Build.0 = Release|arm64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x64.ActiveCfg = Release|x64 + {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x64.Build.0 = Release|x64 {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x86.ActiveCfg = Release|Any CPU {46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x86.Build.0 = Release|Any CPU {CF4A3EBB-18DA-4234-B0DB-3CD45AF4B054}.Debug|Any CPU.ActiveCfg = Debug|x64 diff --git a/WinUI3Localizer/LocalizerBuilder.cs b/WinUI3Localizer/LocalizerBuilder.cs index 98bda66..ac142f8 100644 --- a/WinUI3Localizer/LocalizerBuilder.cs +++ b/WinUI3Localizer/LocalizerBuilder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Xml; @@ -27,6 +28,8 @@ private record StringResourceItems(string Language, IEnumerable Localizer.Get() is Localizer; public LocalizerBuilder SetDefaultStringResourcesFileName(string fileName) @@ -86,6 +89,33 @@ public LocalizerBuilder AddStringResourcesFolderForLanguageDictionaries( return this; } + public LocalizerBuilder AddPriResourcesForLanguageDictionaries( + string[] languages, + string? subTreeName = null, + string? priFile = null) + { + this.builderActions.Add(() => + { + if (this.priResourceReaderFactory == null) + { + this.priResourceReaderFactory = new(); + } + + for (int i = 0; i < languages.Length; i++) + { + PriResourceReader? reader = this.priResourceReaderFactory.GetPriResourceReader(priFile); + + LanguageDictionary? dictionary = new(languages[i]); + foreach (LanguageDictionary.Item item in reader.GetItems(languages[i], subTreeName)) + { + dictionary.AddItem(item); + } + this.languageDictionaries.Add(dictionary); + } + }); + return this; + } + public LocalizerBuilder AddLanguageDictionary(LanguageDictionary dictionary) { this.builderActions.Add(() => this.languageDictionaries.Add(dictionary)); @@ -167,17 +197,20 @@ private static LanguageDictionary CreateLanguageDictionaryFromStringResourceItem return dictionary; } - private static LanguageDictionary.Item CreateLanguageDictionaryItem(StringResourceItem stringResourceItem) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static LanguageDictionary.Item CreateLanguageDictionaryItem(StringResourceItem stringResourceItem) => + CreateLanguageDictionaryItem(stringResourceItem.Name, stringResourceItem.Value); + + internal static LanguageDictionary.Item CreateLanguageDictionaryItem(string name, string value) { - string name = stringResourceItem.Name; (string Uid, string DependencyPropertyName) = name.IndexOf(".") is int firstSeparatorIndex && firstSeparatorIndex > 1 ? (name[..firstSeparatorIndex], string.Concat(name.AsSpan(firstSeparatorIndex + 1), "Property")) : (name, string.Empty); return new LanguageDictionary.Item( Uid, DependencyPropertyName, - stringResourceItem.Value, - stringResourceItem.Name); + value, + name); } private static StringResourceItems? CreateStringResourceItemsFromResourcesFile(string sourceName, string filePath, string xPath = "//root/data") diff --git a/WinUI3Localizer/PriResourceReader.cs b/WinUI3Localizer/PriResourceReader.cs new file mode 100644 index 0000000..3d6b8eb --- /dev/null +++ b/WinUI3Localizer/PriResourceReader.cs @@ -0,0 +1,97 @@ +using Microsoft.Windows.ApplicationModel.Resources; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WinUI3Localizer; +internal class PriResourceReader +{ + private readonly ResourceManager resourceManager; + + internal PriResourceReader(ResourceManager resourceManager) + { + this.resourceManager = resourceManager; + } + + public IEnumerable GetItems(string language, string subTreeName = "Resources") + { + if (string.IsNullOrEmpty(subTreeName) || subTreeName == "/") + { + subTreeName = "Resources"; + } + else if (subTreeName.EndsWith('/')) + { + subTreeName = subTreeName[..^1]; + } + + ResourceMap resourceMap = this.resourceManager.MainResourceMap.TryGetSubtree(subTreeName); + if (resourceMap != null) + { + ResourceContext resourceContext = this.resourceManager.CreateResourceContext(); + resourceContext.QualifierValues[KnownResourceQualifierName.Language] = language; + + return GetItemsCore(resourceMap, subTreeName, resourceContext); + } + + return Enumerable.Empty(); + } + + + private IEnumerable GetItemsCore(ResourceMap resourceMap, string subTreeName, ResourceContext resourceContext) + { + bool isResourcesSubTree = string.Equals(subTreeName, "Resources", StringComparison.OrdinalIgnoreCase); + uint count = resourceMap.ResourceCount; + + for (uint i = 0; i < count; i++) + { + (string key, ResourceCandidate? candidate) = resourceMap.GetValueByIndex(i, resourceContext); + + if (candidate != null && candidate.Kind == ResourceCandidateKind.String) + { + key = key.Replace('/', '.'); + if (!isResourcesSubTree) + { + key = $"/{subTreeName}/{key}"; + } + yield return LocalizerBuilder.CreateLanguageDictionaryItem(key, candidate.ValueAsString); + } + } + } + +} + +internal class PriResourceReaderFactory +{ + private readonly Dictionary readers = new Dictionary(); + + internal PriResourceReader GetPriResourceReader(string? priFile) + { + string? normalizedFilePath = string.Empty; + + if (!string.IsNullOrEmpty(priFile)) + { + normalizedFilePath = System.IO.Path.GetFullPath(priFile); + } + + if (!this.readers.TryGetValue(normalizedFilePath, out PriResourceReader? reader)) + { + ResourceManager manager; + if (string.IsNullOrEmpty(normalizedFilePath)) + { + manager = new ResourceManager(); + } + else + { + manager = new ResourceManager(normalizedFilePath); + } + reader = new PriResourceReader(manager); + this.readers[normalizedFilePath] = reader; + } + + return reader; + } +} +