Skip to content

Commit 03de65c

Browse files
committed
feat(localization): add Blazor language selector and multi-language support #70 #69 #68
1 parent ba529f5 commit 03de65c

23 files changed

Lines changed: 748 additions & 44 deletions

src/Trion.Desktop/Components/Layout/MainLayout.razor

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
@inherits LayoutComponentBase
1+
2+
@inherits LayoutComponentBase
23

34
<MudThemeProvider Theme="@_theme" IsDarkMode="_isDarkMode" />
45
<MudPopoverProvider />
@@ -9,6 +10,7 @@
910
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
1011
<MudText Typo="Typo.h5" Class="ml-3">Application</MudText>
1112
<MudSpacer />
13+
<LanguageSelector/>
1214
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle" />
1315
<MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" />
1416
</MudAppBar>

src/Trion.Desktop/Components/Pages/Home.razor

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ Welcome to your new app.
66

77
@inject TrionLogger Log
88

9-
@* <Button class="btn btn-primary" @onclick="Fire">Log something</Button> *@
9+
@inject Trion.UI.Localization.GlobalLocalizer L
10+
11+
<h1>@L["H_World"]</h1>
12+
<p>Culture: @System.Globalization.CultureInfo.CurrentUICulture.Name</p>
1013

1114
@code{
1215
private void Fire()

src/Trion.Desktop/Components/_Imports.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
@using MudBlazor
1111
@using MudBlazor.Components
1212
@using MudBlazor.Services
13-
@using Trion.Core.Logging
13+
@using Trion.Core.Logging
14+
@using Trion.UI.Localization

src/Trion.Desktop/MauiProgram.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using Microsoft.Extensions.Configuration;
22
using Microsoft.Extensions.Logging;
33
using MudBlazor.Services;
4+
using System.Globalization;
45
using Trion.Core.Logging;
56
using Trion.Core.Monitoring;
7+
using Trion.UI.Localization;
68

79
namespace Trion.Desktop
810
{
@@ -40,7 +42,20 @@ public static MauiApp CreateMauiApp()
4042
builder.Services.AddSingleton<ProcessStore>();
4143
builder.Services.AddHostedService<ProcessMonitor>();
4244

45+
/* Localization */
46+
builder.Services.AddLocalization(options => options.ResourcesPath = "Localization/Resources");
47+
builder.Services.AddSingleton<GlobalLocalizer>();
48+
49+
// Load the last selected language (saved by LanguageSelector)
50+
var savedLang = Preferences.Get("trion_lang", "en");
51+
var culture = new CultureInfo(savedLang);
52+
53+
CultureInfo.CurrentCulture = culture;
54+
CultureInfo.CurrentUICulture = culture;
55+
CultureInfo.DefaultThreadCurrentCulture = culture;
56+
CultureInfo.DefaultThreadCurrentUICulture = culture;
57+
4358
return builder.Build();
4459
}
4560
}
46-
}
61+
}

src/Trion.Desktop/Trion.Desktop.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@
3737

3838
<!-- Core MAUI packages (supports both Windows and Linux GTK) -->
3939
<ItemGroup>
40+
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.10" />
4041
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.10" />
41-
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
42-
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)" />
43-
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.0" />
42+
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.120" />
43+
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.120" />
44+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.10" />
4445
</ItemGroup>
4546

4647
<!-- UI framework -->
@@ -49,5 +50,6 @@
4950
</ItemGroup>
5051
<ItemGroup>
5152
<ProjectReference Include="..\Trion.Core\Trion.Core.csproj" />
53+
<ProjectReference Include="..\Trion.UI\Trion.UI.csproj" />
5254
</ItemGroup>
5355
</Project>

src/Trion.Desktop/wwwroot/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
66
<title>Trion.Desktop</title>
77
<base href="/" />
8+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.3.2/css/flag-icons.min.css" />
89
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
910
<link rel="stylesheet" href="css/app.css" />
1011
<link rel="stylesheet" href="Trion.Desktop.styles.css" />
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.Extensions.Localization;
2+
3+
namespace Trion.UI.Localization
4+
{
5+
public class GlobalLocalizer
6+
{
7+
private readonly IStringLocalizer _localizer;
8+
9+
public GlobalLocalizer(IStringLocalizerFactory factory)
10+
{
11+
// IMPORTANT:
12+
// set ResourcesPath = "Localization/Resources" in the host apps.
13+
// With that, using base name "Strings" here is correct.
14+
var assemblyName = typeof(GlobalLocalizer).Assembly.GetName().Name!;
15+
_localizer = factory.Create("Strings", assemblyName);
16+
}
17+
18+
public string this[string key] => _localizer[key];
19+
}
20+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
@rendermode InteractiveServer
2+
@using MudBlazor
3+
@using System.Globalization
4+
@using Microsoft.AspNetCore.Http
5+
@inject NavigationManager Nav
6+
@inject IHttpContextAccessor Http
7+
8+
<MudMenu Dense="true" OffsetY="true"
9+
AnchorOrigin="Origin.BottomCenter" TransformOrigin="Origin.TopCenter">
10+
<ActivatorContent>
11+
<div style="display:flex; align-items:center; gap:6px; cursor:pointer;">
12+
<span class="fi @($"fi-{GetFlagCode(CurrentLang)}")" style="font-size:1.4rem;"></span>
13+
<span style="font-size:0.9rem;">@GetDisplayName(CurrentLang)</span>
14+
</div>
15+
</ActivatorContent>
16+
17+
<ChildContent>
18+
@foreach (var lang in _available)
19+
{
20+
<MudMenuItem OnClick="() => ChangeLanguage(lang)">
21+
<div style="display:flex; align-items:center; gap:8px;">
22+
<span class="fi @($"fi-{GetFlagCode(lang)}")" style="font-size:1.2rem;"></span>
23+
<span>@GetDisplayName(lang)</span>
24+
</div>
25+
</MudMenuItem>
26+
}
27+
</ChildContent>
28+
</MudMenu>
29+
30+
@code {
31+
private string[] _available = new[] { "en", "de", "ro" };
32+
private string CurrentLang => CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
33+
34+
private async Task ChangeLanguage(string lang)
35+
{
36+
if (Http?.HttpContext != null)
37+
{
38+
var relative = Nav.ToBaseRelativePath(Nav.Uri);
39+
if (string.IsNullOrWhiteSpace(relative)) relative = "/";
40+
Nav.NavigateTo($"/Culture/Set?culture={lang}&redirectUri=/{relative}", forceLoad: true);
41+
}
42+
await Task.CompletedTask;
43+
}
44+
45+
private static string GetDisplayName(string lang)
46+
{
47+
try
48+
{
49+
var c = new CultureInfo(lang);
50+
return char.ToUpper(c.NativeName[0]) + c.NativeName[1..];
51+
}
52+
catch
53+
{
54+
return lang.ToUpper();
55+
}
56+
}
57+
58+
private static string GetFlagCode(string lang)
59+
{
60+
try
61+
{
62+
var culture = new CultureInfo(lang);
63+
64+
// Case 1: specific culture like de-DE or fr-FR
65+
if (culture.Name.Contains('-'))
66+
{
67+
var region = culture.Name.Split('-').Last();
68+
return region.ToLower();
69+
}
70+
71+
// Case 2: neutral languages (no region)
72+
return culture.TwoLetterISOLanguageName.ToLower() switch
73+
{
74+
"en" => "us", // 🇺🇸 English (change to "gb" for 🇬🇧)
75+
"pt" => "pt",
76+
"es" => "es",
77+
"zh" => "cn",
78+
_ => culture.TwoLetterISOLanguageName.ToLower()
79+
};
80+
}
81+
catch
82+
{
83+
return "xx";
84+
}
85+
}
86+
}

src/Trion.UI/Localization/Resources/Strings.Designer.cs

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)