Skip to content

Commit 1674d3e

Browse files
Update WinUI3Localizer NuGet package description
1 parent c90e2fc commit 1674d3e

File tree

4 files changed

+265
-106
lines changed

4 files changed

+265
-106
lines changed

README.md

Lines changed: 184 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
# 🌏WinUI3Localizer
2-
The WinUI3Localizer is a NuGet package that helps you localize your WinUI 3 app.
32

4-
- Switch languages **without restarting**
3+
**WinUI3Localizer** is a NuGet package that helps you localize your WinUI 3 app.
4+
5+
- Switch languages **without app restarting**
56
- You/users can **edit** localized strings even after deployment
67
- You/users can **add** new languages even after deployment
7-
- Use standard **Resources.resw**
8+
- Use standard **Resources.resw** (see [Microsoft docs](https://learn.microsoft.com/en-us/windows/uwp/app-resources/localize-strings-ui-manifest))
89

9-
## 🙌 Getting started
10+
## 🙌 Quick Start
1011

11-
### Installing the WinUI3Localizer
12-
Install the WinUI3Localizer from the NuGet Package Manager or using the command below.
12+
> **Note**: This is a quick start guide. Check the sample app for details.
1313
14-
```powershell
15-
dotnet add package WinUI3Localizer
16-
```
14+
### **Install WinUI3Localizer**
15+
16+
Install **WinUI3Localizer** from the NuGet Package Manager.
1717

18-
### Prepare the "Strings" folder
19-
Create a "Strings" folder and populate it with your string resources files for the languages you need. For example, this is the basic structure of a "Strings" folder for English (en-US), es-ES (Spanish) and Japanese (ja).
18+
### **Create localized strings**
19+
20+
Create a "Strings" folder in your app project and populate it with your string resources files for each language you need. For example, this is a basic structure for English (en-US), es-ES (Spanish) and Japanese (ja) resources files.
2021

2122
- Strings
2223
- en-US
@@ -26,4 +27,175 @@ Create a "Strings" folder and populate it with your string resources files for t
2627
- ja
2728
- Resources.resw
2829

29-
The "Strings" folder can be anywhere as long the app can access it. Usually, aside the app executable for non-packaged apps, or in the LocalData folder for packaged-apps.
30+
Add this ItemGroup in the project file (\*.csproj) of your app.
31+
32+
```xml
33+
<!-- Copy all "Resources.resw" files in the "Strings" folder to the output folder. -->
34+
<ItemGroup>
35+
<Content Include="Strings\**\*.resw">
36+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
37+
</Content>
38+
</ItemGroup>
39+
```
40+
41+
> **Note**: The "Strings" folder can be anywhere as long the app can access it. Usually, aside the app executable for non-packaged apps, or in the "LocalFolder" for packaged-apps.
42+
43+
### **Build WinUI3Localizer**
44+
45+
- Non-packaged apps:
46+
47+
In App.xaml.cs, build **WinUI3Localizer** like this:
48+
49+
```csharp
50+
private async Task InitializeLocalizer()
51+
{
52+
// Initialize a "Strings" folder in the executables folder.
53+
StringsFolderPath = Path.Combine(Directory.GetCurrentDirectory(), "Strings");
54+
StorageFolder stringsFolder = await StorageFolder.GetFolderFromPathAsync(StringsFolderPath);
55+
56+
ILocalizer localizer = await new LocalizerBuilder()
57+
.AddStringResourcesFolderForLanguageDictionaries(StringsFolderPath)
58+
.SetOptions(options =>
59+
{
60+
options.DefaultLanguage = "en-US";
61+
options.UseUidWhenLocalizedStringNotFound = true;
62+
})
63+
.Build();
64+
}
65+
```
66+
67+
- Packaged apps:
68+
69+
In App.xaml.cs, build **WinUI3Localizer** like this:
70+
71+
```csharp
72+
private async Task InitializeLocalizer()
73+
{
74+
75+
// Initialize a "Strings" folder in the "LocalFolder" for the packaged app.
76+
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
77+
StorageFolder stringsFolder = await localFolder.CreateFolderAsync(
78+
"Strings",
79+
CreationCollisionOption.OpenIfExists);
80+
81+
// Create string resources file from app resources if doesn't exists.
82+
string resourceFileName = "Resources.resw";
83+
await CreateStringResourceFileIfNotExists(stringsFolder, "en-US", resourceFileName);
84+
await CreateStringResourceFileIfNotExists(stringsFolder, "es-ES", resourceFileName);
85+
await CreateStringResourceFileIfNotExists(stringsFolder, "ja", resourceFileName);
86+
87+
ILocalizer localizer = await new LocalizerBuilder()
88+
.AddStringResourcesFolderForLanguageDictionaries(stringsFolder.Path)
89+
.SetOptions(options =>
90+
{
91+
options.DefaultLanguage = "en-US";
92+
options.UseUidWhenLocalizedStringNotFound = true;
93+
})
94+
.Build();
95+
}
96+
97+
private static async Task CreateStringResourceFileIfNotExists(StorageFolder stringsFolder, string language, string resourceFileName)
98+
{
99+
StorageFolder languageFolder = await stringsFolder.CreateFolderAsync(
100+
language,
101+
CreationCollisionOption.OpenIfExists);
102+
103+
if (await languageFolder.TryGetItemAsync(resourceFileName) is null)
104+
{
105+
string resourceFilePath = Path.Combine(stringsFolder.Name, language, resourceFileName);
106+
StorageFile resourceFile = await LoadStringResourcesFileFromAppResource(resourceFilePath);
107+
_ = await resourceFile.CopyAsync(languageFolder);
108+
}
109+
}
110+
111+
private static async Task<StorageFile> LoadStringResourcesFileFromAppResource(string filePath)
112+
{
113+
Uri resourcesFileUri = new($"ms-appx:///{filePath}");
114+
return await StorageFile.GetFileFromApplicationUriAsync(resourcesFileUri);
115+
}
116+
```
117+
118+
### **Localizing controls**
119+
120+
This is an example of how to localize the `Content` of a `Button`.
121+
122+
First, we need to asign an `Uid` to the `Button`.
123+
124+
```xml
125+
<Page
126+
x:Class="WinUI3Localizer.SampleApp.TestPage"
127+
...
128+
xmlns:l="using:WinUI3Localizer">
129+
<StackPanel>
130+
<Button l:Uids.Uid="TestPage_Button" />
131+
</StackPanel>
132+
</Page>
133+
```
134+
135+
Then in each language resources file, we need to add an item that corresponds to the `Uid`.
136+
137+
- en-US
138+
139+
- Resources.resw
140+
| Name | Value|
141+
| ---- | ---- |
142+
| TestPageButton.Content | Awesome! |
143+
144+
- es-ES:
145+
146+
- Resources.resw
147+
| Name | Value|
148+
| ---- | ---- |
149+
| TestPageButton.Content | ¡Increíble! |
150+
151+
- ja:
152+
- Resources.resw
153+
| Name | Value|
154+
| ---- | ---- |
155+
| TestPageButton.Content | 素晴らしい! |
156+
157+
### **Getting localized strings**
158+
159+
If we need to localize strings in code-behind or in ViewModels, we can use the `GetLocalizedString()` method.
160+
161+
```csharp
162+
List<string> colors = new()
163+
{
164+
"Red",
165+
"Green",
166+
"Blue",
167+
};
168+
169+
ILocalizer localizer = Localizer.Get();
170+
List<string> localizedColors = colors
171+
.Select(x => localizer.GetLocalizedString(x))
172+
.ToList();
173+
```
174+
175+
In this case, we just use the `Uid` as `Name`.
176+
177+
- en-US
178+
179+
- Resources.resw
180+
| Name | Value|
181+
| ---- | ---- |
182+
| Red | Red |
183+
| Green | Green |
184+
| Blue | Blue |
185+
186+
- es-ES:
187+
188+
- Resources.resw
189+
| Name | Value|
190+
| ---- | ---- |
191+
| Red | Rojo |
192+
| Green | Verde |
193+
| Blue | Azul |
194+
195+
- ja:
196+
- Resources.resw
197+
| Name | Value|
198+
| ---- | ---- |
199+
| Red ||
200+
| Green ||
201+
| Blue ||

WinUI3Localizer.SampleApp/App.xaml.cs

Lines changed: 68 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,28 @@
22
using Microsoft.Extensions.Hosting;
33
using Microsoft.Extensions.Logging;
44
using Microsoft.UI.Xaml;
5-
using Microsoft.UI.Xaml.Documents;
65
using Serilog;
76
using System;
87
using System.IO;
98
using System.Threading.Tasks;
109
using Windows.Storage;
11-
using WinUI3Localizer;
1210

1311
namespace WinUI3Localizer.SampleApp;
1412

1513
public partial class App : Application
1614
{
17-
private static IHost Host { get; } = BuildHost();
18-
19-
public static string StringsFolderPath { get; private set; } = string.Empty;
20-
2115
private Window? window;
2216

23-
private string[] DefaultStringsResources { get; } = { "en-US", "es-ES", "ja" };
24-
2517
public App()
2618
{
2719
InitializeComponent();
2820
RequestedTheme = ApplicationTheme.Dark;
2921
}
3022

23+
public static string StringsFolderPath { get; private set; } = string.Empty;
24+
25+
private static IHost Host { get; } = BuildHost();
26+
3127
protected override async void OnLaunched(LaunchActivatedEventArgs args)
3228
{
3329
await InitializeLocalizer();
@@ -36,69 +32,24 @@ protected override async void OnLaunched(LaunchActivatedEventArgs args)
3632
this.window.Activate();
3733
}
3834

39-
private async Task PrepareDefaultStringsResourcesFolderForPackagedApps(StorageFolder stringsFolder)
35+
private static async Task CreateStringResourceFileIfNotExists(StorageFolder stringsFolder, string language, string resourceFileName)
4036
{
41-
foreach (string language in DefaultStringsResources)
42-
{
43-
StorageFolder languageFolder = await stringsFolder.CreateFolderAsync(
44-
language,
45-
CreationCollisionOption.OpenIfExists);
37+
StorageFolder languageFolder = await stringsFolder.CreateFolderAsync(
38+
language,
39+
CreationCollisionOption.OpenIfExists);
4640

47-
string resourcesFileName = "Resources.resw";
48-
49-
if (await languageFolder.TryGetItemAsync(resourcesFileName) is null)
50-
{
51-
Uri resourcesFileUri = new($"ms-appx:///Strings/{language}/{resourcesFileName}");
52-
StorageFile defaultResourceFile = await StorageFile.GetFileFromApplicationUriAsync(resourcesFileUri);
53-
_ = await defaultResourceFile.CopyAsync(languageFolder);
54-
}
41+
if (await languageFolder.TryGetItemAsync(resourceFileName) is null)
42+
{
43+
string resourceFilePath = Path.Combine(stringsFolder.Name, language, resourceFileName);
44+
StorageFile resourceFile = await LoadStringResourcesFileFromAppResource(resourceFilePath);
45+
_ = await resourceFile.CopyAsync(languageFolder);
5546
}
5647
}
5748

58-
/// <summary>
59-
/// Creates default Resources.resw files for the WinUI3Localizer.
60-
/// </summary>
61-
private async Task InitializeLocalizer()
49+
private static async Task<StorageFile> LoadStringResourcesFileFromAppResource(string filePath)
6250
{
63-
#if IS_NON_PACKAGED
64-
// Initialize a "Strings" folder in the executables folder.
65-
StringsFolderPath = Path.Combine(Directory.GetCurrentDirectory(), "Strings");
66-
//StorageFolder localFolder = await StorageFolder.GetFolderFromPathAsync(Directory.GetCurrentDirectory());
67-
StorageFolder stringsFolder = await StorageFolder.GetFolderFromPathAsync(StringsFolderPath);
68-
#else
69-
// Initialize a "Strings" folder in the "LocalFolder" for the packaged app.
70-
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
71-
StorageFolder stringsFolder = await localFolder.CreateFolderAsync("Strings", CreationCollisionOption.OpenIfExists);
72-
StringsFolderPath = stringsFolder.Path;
73-
await PrepareDefaultStringsResourcesFolderForPackagedApps(stringsFolder);
74-
#endif
75-
76-
ILocalizer localizer = await new LocalizerBuilder()
77-
.AddStringResourcesFolderForLanguageDictionaries(StringsFolderPath)
78-
//.SetLogger(Host.Services
79-
// .GetRequiredService<ILoggerFactory>()
80-
// .CreateLogger<Localizer>())
81-
.SetOptions(options =>
82-
{
83-
options.DefaultLanguage = "en-US";
84-
options.UseUidWhenLocalizedStringNotFound = true;
85-
})
86-
//.AddLocalizationAction(new LocalizationActionItem(typeof(Hyperlink), arguments =>
87-
//{
88-
// if (arguments.DependencyObject is Hyperlink target && target.Inlines.Count is 0)
89-
// {
90-
// target.Inlines.Clear();
91-
// target.Inlines.Add(new Run() { Text = arguments.Value });
92-
// }
93-
//}))
94-
//.AddLocalizationAction(new LocalizationActionItem(typeof(Run), arguments =>
95-
//{
96-
// if (arguments.DependencyObject is Run target)
97-
// {
98-
// target.Text = arguments.Value;
99-
// }
100-
//}))
101-
.Build();
51+
Uri resourcesFileUri = new($"ms-appx:///{filePath}");
52+
return await StorageFile.GetFileFromApplicationUriAsync(resourcesFileUri);
10253
}
10354

10455
private static IHost BuildHost()
@@ -144,4 +95,56 @@ private static IHost BuildHost()
14495
})
14596
.Build();
14697
}
98+
99+
/// <summary>
100+
/// Creates default Resources.resw files for WinUI3Localizer.
101+
/// </summary>
102+
private async Task InitializeLocalizer()
103+
{
104+
#if IS_NON_PACKAGED
105+
// Initialize a "Strings" folder in the executables folder.
106+
StringsFolderPath = Path.Combine(Directory.GetCurrentDirectory(), "Strings");
107+
108+
//StorageFolder localFolder = await StorageFolder.GetFolderFromPathAsync(Directory.GetCurrentDirectory());
109+
StorageFolder stringsFolder = await StorageFolder.GetFolderFromPathAsync(StringsFolderPath);
110+
#else
111+
// Initialize a "Strings" folder in the "LocalFolder" for the packaged app.
112+
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
113+
StorageFolder stringsFolder = await localFolder.CreateFolderAsync("Strings", CreationCollisionOption.OpenIfExists);
114+
StringsFolderPath = stringsFolder.Path;
115+
116+
// Create string resources file from app resources if doesn't exists.
117+
string resourceFileName = "Resources.resw";
118+
await CreateStringResourceFileIfNotExists(stringsFolder, "en-US", resourceFileName);
119+
await CreateStringResourceFileIfNotExists(stringsFolder, "es-ES", resourceFileName);
120+
await CreateStringResourceFileIfNotExists(stringsFolder, "ja", resourceFileName);
121+
#endif
122+
123+
ILocalizer localizer = await new LocalizerBuilder()
124+
.AddStringResourcesFolderForLanguageDictionaries(StringsFolderPath)
125+
//.SetLogger(Host.Services
126+
// .GetRequiredService<ILoggerFactory>()
127+
// .CreateLogger<Localizer>())
128+
.SetOptions(options =>
129+
{
130+
options.DefaultLanguage = "en-US";
131+
options.UseUidWhenLocalizedStringNotFound = true;
132+
})
133+
//.AddLocalizationAction(new LocalizationActionItem(typeof(Hyperlink), arguments =>
134+
//{
135+
// if (arguments.DependencyObject is Hyperlink target && target.Inlines.Count is 0)
136+
// {
137+
// target.Inlines.Clear();
138+
// target.Inlines.Add(new Run() { Text = arguments.Value });
139+
// }
140+
//}))
141+
//.AddLocalizationAction(new LocalizationActionItem(typeof(Run), arguments =>
142+
//{
143+
// if (arguments.DependencyObject is Run target)
144+
// {
145+
// target.Text = arguments.Value;
146+
// }
147+
//}))
148+
.Build();
149+
}
147150
}

0 commit comments

Comments
 (0)