Add localization support for multi-language UI

This commit introduces comprehensive localization support for the application, enabling dynamic language switching and localized UI elements. Key changes include:

- Added `.resx` files for German (de-DE), English (en-US), and French (fr-FR).
- Updated Razor components to use `IStringLocalizer` for dynamic string localization.
- Introduced the `L(string key, string fallback)` helper method for consistent localized string retrieval with fallback support.
- Implemented dynamic culture initialization in `Program.cs` via an API call to `/api/Localization/lang`.
- Localized error pages, grid columns, tabs, popups, validation messages, and tooltips.
- Dynamically generated privacy policy links based on the current culture.
- Improved user experience with fully localized loading states, error handling, and UI labels.

These changes enhance accessibility and usability, making the application more user-friendly for a global audience.
This commit is contained in:
2026-09-28 10:46:06 +02:00
parent 48cfd3f416
commit 735f072dd1
13 changed files with 682 additions and 187 deletions

View File

@@ -1,8 +1,14 @@
@using EnvelopeGenerator.Server.Client.Services
@using EnvelopeGenerator.Application.Resources
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.Extensions.Localization
@using System.Globalization
@inherits LayoutComponentBase
@implements IDisposable
@inject ThemeService ThemeService
@inject IStringLocalizer<Resource> UiLocalizer
@inject NavigationManager Navigation
<HeadContent>
<link href="@ThemeService.ThemeCssHref" rel="stylesheet" />
@@ -18,7 +24,7 @@
<div class="receiver-footer__left">
<span>&copy; SignFlow 2023-2024 <a href="https://digitaldata.works" target="_blank" rel="noopener">Digital Data GmbH</a></span>
<span class="receiver-footer__sep">&#124;</span>
<a href="docs/privacy-policy.de-DE.html" target="_blank" rel="noopener">Datenschutz</a>
<a href="@GetPrivacyPolicyPath()" target="_blank" rel="noopener">@L("PrivacyPolicy", "Datenschutz")</a>
</div>
<div class="receiver-footer__right">
@@ -35,6 +41,7 @@
protected override async Task OnInitializedAsync()
{
Navigation.LocationChanged += OnLocationChanged;
await FontLoader.LoadFonts(HttpClientFactory, ["opensans.ttf"]);
_themeChangeHandler = () => InvokeAsync(StateHasChanged);
ThemeService.OnChange += _themeChangeHandler;
@@ -42,5 +49,31 @@
}
public void Dispose()
=> ThemeService.OnChange -= _themeChangeHandler;
{
ThemeService.OnChange -= _themeChangeHandler;
Navigation.LocationChanged -= OnLocationChanged;
}
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
=> _ = InvokeAsync(StateHasChanged);
private string GetPrivacyPolicyPath()
{
var current = CultureInfo.CurrentUICulture.Name;
var code = current switch
{
"de-DE" => "de-DE",
"en-US" => "en-US",
"fr-FR" => "fr-FR",
_ => "de-DE"
};
return $"docs/privacy-policy.{code}.html";
}
string L(string key, string fallback)
{
var value = UiLocalizer[key];
return value.ResourceNotFound ? fallback : value.Value;
}
}