Skip to content

Commit fefc9a0

Browse files
Merge pull request #41 from cnbluefire/main
Add support for pri files
2 parents 0cd51fc + d8cfc79 commit fefc9a0

File tree

7 files changed

+210
-17
lines changed

7 files changed

+210
-17
lines changed

WinUI3Localizer.SampleApp/App.xaml.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ private async Task InitializeLocalizer()
133133
#endif
134134

135135
ILocalizer localizer = await new LocalizerBuilder()
136-
.AddStringResourcesFolderForLanguageDictionaries(StringsFolderPath)
136+
.AddPriResourcesForLanguageDictionaries(new[] { "en-US", "es-ES", "ja" } )
137+
.AddPriResourcesForLanguageDictionaries(new[] { "en-US", "es-ES", "ja" }, "ErrorMessages")
138+
//.AddStringResourcesFolderForLanguageDictionaries(StringsFolderPath)
137139
//.SetLogger(Host.Services
138140
// .GetRequiredService<ILoggerFactory>()
139141
// .CreateLogger<Localizer>())
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using FluentAssertions;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace WinUI3Localizer.Tests;
9+
10+
public class PriResourceReaderTest
11+
{
12+
[Fact]
13+
public void GetItems_ReturnsAllItems()
14+
{
15+
LanguageDictionary.Item[]? resourcesItems = null;
16+
LanguageDictionary.Item[]? errorMessagesItems = null;
17+
18+
Thread? thread = new Thread(() =>
19+
{
20+
Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap.Initialize(0x00010005);
21+
22+
string? priFile = Path.Combine(AppContext.BaseDirectory, "resources.pri");
23+
PriResourceReaderFactory? factory = new();
24+
25+
PriResourceReader? reader = factory.GetPriResourceReader(priFile);
26+
27+
resourcesItems = reader.GetItems("en-US").ToArray();
28+
errorMessagesItems = reader.GetItems("es-ES", "ErrorMessages").ToArray();
29+
30+
Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap.Shutdown();
31+
});
32+
33+
thread.SetApartmentState(ApartmentState.STA);
34+
thread.Start();
35+
thread.Join();
36+
37+
LanguageDictionary.Item item1 = new LanguageDictionary.Item("ControlsPage_Button", "ContentProperty", "Click", "ControlsPage_Button.Content");
38+
LanguageDictionary.Item item2 = new LanguageDictionary.Item("StylesPage_Top", "TextProperty", "Top", "StylesPage_Top.Text");
39+
LanguageDictionary.Item item3 = new LanguageDictionary.Item("/ErrorMessages/ErrorMessageExample", "TextProperty", "Ejemplo de mensajes de error", "/ErrorMessages/ErrorMessageExample.Text");
40+
41+
resourcesItems.Should().HaveCount(55);
42+
resourcesItems.Should().Contain(item1);
43+
resourcesItems.Should().Contain(item2);
44+
resourcesItems.Should().NotContain(item3);
45+
46+
errorMessagesItems.Should().HaveCount(1);
47+
errorMessagesItems.Should().NotContain(item1);
48+
errorMessagesItems.Should().NotContain(item2);
49+
errorMessagesItems.Should().Contain(item3);
50+
}
51+
52+
}

WinUI3Localizer.Tests/WinUI3Localizer.Tests.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<IsPackable>false</IsPackable>
8+
<Platforms>x86;x64;arm64</Platforms>
9+
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
810
</PropertyGroup>
911

1012
<ItemGroup>
1113
<PackageReference Include="FluentAssertions" Version="6.9.0" />
1214
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
15+
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240404000" />
1316
<PackageReference Include="Moq" Version="4.18.4" />
1417
<PackageReference Include="xunit" Version="2.4.2" />
1518
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
@@ -30,4 +33,10 @@
3033
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
3134
</ItemGroup>
3235

36+
<ItemGroup>
37+
<None Update="resources.pri">
38+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
39+
</None>
40+
</ItemGroup>
41+
3342
</Project>
66.5 KB
Binary file not shown.

WinUI3Localizer.sln

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ Global
4646
{C1E60FCC-8B43-4190-ADF1-A28B954F8DEB}.Release|x64.Build.0 = Release|Any CPU
4747
{C1E60FCC-8B43-4190-ADF1-A28B954F8DEB}.Release|x86.ActiveCfg = Release|Any CPU
4848
{C1E60FCC-8B43-4190-ADF1-A28B954F8DEB}.Release|x86.Build.0 = Release|Any CPU
49-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|Any CPU.Build.0 = Debug|Any CPU
51-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|ARM64.ActiveCfg = Debug|Any CPU
52-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|ARM64.Build.0 = Debug|Any CPU
53-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x64.ActiveCfg = Debug|Any CPU
54-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x64.Build.0 = Debug|Any CPU
49+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|Any CPU.ActiveCfg = Debug|x64
50+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|Any CPU.Build.0 = Debug|x64
51+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|ARM64.ActiveCfg = Debug|arm64
52+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|ARM64.Build.0 = Debug|arm64
53+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x64.ActiveCfg = Debug|x64
54+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x64.Build.0 = Debug|x64
5555
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x86.ActiveCfg = Debug|Any CPU
5656
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Debug|x86.Build.0 = Debug|Any CPU
57-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|Any CPU.ActiveCfg = Release|Any CPU
58-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|Any CPU.Build.0 = Release|Any CPU
59-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|ARM64.ActiveCfg = Release|Any CPU
60-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|ARM64.Build.0 = Release|Any CPU
61-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x64.ActiveCfg = Release|Any CPU
62-
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x64.Build.0 = Release|Any CPU
57+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|Any CPU.ActiveCfg = Release|x64
58+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|Any CPU.Build.0 = Release|x64
59+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|ARM64.ActiveCfg = Release|arm64
60+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|ARM64.Build.0 = Release|arm64
61+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x64.ActiveCfg = Release|x64
62+
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x64.Build.0 = Release|x64
6363
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x86.ActiveCfg = Release|Any CPU
6464
{46D3D143-8BBF-4A74-960D-8CD34F35B58E}.Release|x86.Build.0 = Release|Any CPU
6565
{CF4A3EBB-18DA-4234-B0DB-3CD45AF4B054}.Debug|Any CPU.ActiveCfg = Debug|x64

WinUI3Localizer/LocalizerBuilder.cs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.Collections.Generic;
44
using System.IO;
5+
using System.Runtime.CompilerServices;
56
using System.Threading.Tasks;
67
using System.Xml;
78

@@ -27,6 +28,8 @@ private record StringResourceItems(string Language, IEnumerable<StringResourceIt
2728

2829
private ILogger? logger;
2930

31+
private PriResourceReaderFactory? priResourceReaderFactory;
32+
3033
public static bool IsLocalizerAlreadyBuilt => Localizer.Get() is Localizer;
3134

3235
public LocalizerBuilder SetDefaultStringResourcesFileName(string fileName)
@@ -86,6 +89,33 @@ public LocalizerBuilder AddStringResourcesFolderForLanguageDictionaries(
8689
return this;
8790
}
8891

92+
public LocalizerBuilder AddPriResourcesForLanguageDictionaries(
93+
string[] languages,
94+
string? subTreeName = null,
95+
string? priFile = null)
96+
{
97+
this.builderActions.Add(() =>
98+
{
99+
if (this.priResourceReaderFactory == null)
100+
{
101+
this.priResourceReaderFactory = new();
102+
}
103+
104+
for (int i = 0; i < languages.Length; i++)
105+
{
106+
PriResourceReader? reader = this.priResourceReaderFactory.GetPriResourceReader(priFile);
107+
108+
LanguageDictionary? dictionary = new(languages[i]);
109+
foreach (LanguageDictionary.Item item in reader.GetItems(languages[i], subTreeName))
110+
{
111+
dictionary.AddItem(item);
112+
}
113+
this.languageDictionaries.Add(dictionary);
114+
}
115+
});
116+
return this;
117+
}
118+
89119
public LocalizerBuilder AddLanguageDictionary(LanguageDictionary dictionary)
90120
{
91121
this.builderActions.Add(() => this.languageDictionaries.Add(dictionary));
@@ -167,17 +197,20 @@ private static LanguageDictionary CreateLanguageDictionaryFromStringResourceItem
167197
return dictionary;
168198
}
169199

170-
private static LanguageDictionary.Item CreateLanguageDictionaryItem(StringResourceItem stringResourceItem)
200+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
201+
private static LanguageDictionary.Item CreateLanguageDictionaryItem(StringResourceItem stringResourceItem) =>
202+
CreateLanguageDictionaryItem(stringResourceItem.Name, stringResourceItem.Value);
203+
204+
internal static LanguageDictionary.Item CreateLanguageDictionaryItem(string name, string value)
171205
{
172-
string name = stringResourceItem.Name;
173206
(string Uid, string DependencyPropertyName) = name.IndexOf(".") is int firstSeparatorIndex && firstSeparatorIndex > 1
174207
? (name[..firstSeparatorIndex], string.Concat(name.AsSpan(firstSeparatorIndex + 1), "Property"))
175208
: (name, string.Empty);
176209
return new LanguageDictionary.Item(
177210
Uid,
178211
DependencyPropertyName,
179-
stringResourceItem.Value,
180-
stringResourceItem.Name);
212+
value,
213+
name);
181214
}
182215

183216
private static StringResourceItems? CreateStringResourceItemsFromResourcesFile(string sourceName, string filePath, string xPath = "//root/data")
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using Microsoft.Windows.ApplicationModel.Resources;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace WinUI3Localizer;
10+
internal class PriResourceReader
11+
{
12+
private readonly ResourceManager resourceManager;
13+
14+
internal PriResourceReader(ResourceManager resourceManager)
15+
{
16+
this.resourceManager = resourceManager;
17+
}
18+
19+
public IEnumerable<LanguageDictionary.Item> GetItems(string language, string subTreeName = "Resources")
20+
{
21+
if (string.IsNullOrEmpty(subTreeName) || subTreeName == "/")
22+
{
23+
subTreeName = "Resources";
24+
}
25+
else if (subTreeName.EndsWith('/'))
26+
{
27+
subTreeName = subTreeName[..^1];
28+
}
29+
30+
ResourceMap resourceMap = this.resourceManager.MainResourceMap.TryGetSubtree(subTreeName);
31+
if (resourceMap != null)
32+
{
33+
ResourceContext resourceContext = this.resourceManager.CreateResourceContext();
34+
resourceContext.QualifierValues[KnownResourceQualifierName.Language] = language;
35+
36+
return GetItemsCore(resourceMap, subTreeName, resourceContext);
37+
}
38+
39+
return Enumerable.Empty<LanguageDictionary.Item>();
40+
}
41+
42+
43+
private IEnumerable<LanguageDictionary.Item> GetItemsCore(ResourceMap resourceMap, string subTreeName, ResourceContext resourceContext)
44+
{
45+
bool isResourcesSubTree = string.Equals(subTreeName, "Resources", StringComparison.OrdinalIgnoreCase);
46+
uint count = resourceMap.ResourceCount;
47+
48+
for (uint i = 0; i < count; i++)
49+
{
50+
(string key, ResourceCandidate? candidate) = resourceMap.GetValueByIndex(i, resourceContext);
51+
52+
if (candidate != null && candidate.Kind == ResourceCandidateKind.String)
53+
{
54+
key = key.Replace('/', '.');
55+
if (!isResourcesSubTree)
56+
{
57+
key = $"/{subTreeName}/{key}";
58+
}
59+
yield return LocalizerBuilder.CreateLanguageDictionaryItem(key, candidate.ValueAsString);
60+
}
61+
}
62+
}
63+
64+
}
65+
66+
internal class PriResourceReaderFactory
67+
{
68+
private readonly Dictionary<string, PriResourceReader> readers = new Dictionary<string, PriResourceReader>();
69+
70+
internal PriResourceReader GetPriResourceReader(string? priFile)
71+
{
72+
string? normalizedFilePath = string.Empty;
73+
74+
if (!string.IsNullOrEmpty(priFile))
75+
{
76+
normalizedFilePath = System.IO.Path.GetFullPath(priFile);
77+
}
78+
79+
if (!this.readers.TryGetValue(normalizedFilePath, out PriResourceReader? reader))
80+
{
81+
ResourceManager manager;
82+
if (string.IsNullOrEmpty(normalizedFilePath))
83+
{
84+
manager = new ResourceManager();
85+
}
86+
else
87+
{
88+
manager = new ResourceManager(normalizedFilePath);
89+
}
90+
reader = new PriResourceReader(manager);
91+
this.readers[normalizedFilePath] = reader;
92+
}
93+
94+
return reader;
95+
}
96+
}
97+

0 commit comments

Comments
 (0)