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:
@@ -1,14 +1,17 @@
|
||||
@using EnvelopeGenerator.Server.Client.Services
|
||||
@using EnvelopeGenerator.Application.Resources
|
||||
@using Microsoft.Extensions.Localization
|
||||
@rendermode InteractiveWebAssembly
|
||||
|
||||
@inject LocalizationService LocalizationService
|
||||
@inject NavigationManager Navigation
|
||||
@inject IStringLocalizer<Resource> UiLocalizer
|
||||
|
||||
<div class="sf-lang-combo">
|
||||
<button type="button"
|
||||
class="sf-lang-trigger"
|
||||
@onclick="ToggleMenu"
|
||||
aria-label="Language"
|
||||
aria-label="@L("Language", "Language")"
|
||||
aria-expanded="@_isOpen">
|
||||
<span class="fi @_selected.FlagClass"></span>
|
||||
</button>
|
||||
@@ -17,11 +20,11 @@
|
||||
{
|
||||
<button type="button"
|
||||
class="sf-lang-backdrop"
|
||||
aria-label="Close language menu"
|
||||
aria-label="@L("CloseLanguageMenu", "Close language menu")"
|
||||
@onclick="CloseMenu">
|
||||
</button>
|
||||
|
||||
<div class="sf-lang-menu" role="listbox" aria-label="Languages">
|
||||
<div class="sf-lang-menu" role="listbox" aria-label="@L("Languages", "Languages")">
|
||||
@foreach (var option in _options)
|
||||
{
|
||||
<button type="button"
|
||||
@@ -81,4 +84,10 @@
|
||||
}
|
||||
|
||||
private sealed record LanguageOption(string Code, string FlagClass, string ShortName);
|
||||
|
||||
string L(string key, string fallback)
|
||||
{
|
||||
var value = UiLocalizer[key];
|
||||
return value.ResourceNotFound ? fallback : value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>© SignFlow 2023-2024 <a href="https://digitaldata.works" target="_blank" rel="noopener">Digital Data GmbH</a></span>
|
||||
<span class="receiver-footer__sep">|</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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
@page "/"
|
||||
@using EnvelopeGenerator.Application.Resources
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inject IJSRuntime JS
|
||||
@inject NavigationManager Navigation
|
||||
@inject EnvelopeGenerator.Server.Client.Services.CustomImagesService CustomImagesService
|
||||
@inject IStringLocalizer<Resource> UiLocalizer
|
||||
@rendermode InteractiveWebAssembly
|
||||
|
||||
<div class="home-page-wrapper">
|
||||
@@ -24,7 +27,7 @@
|
||||
}
|
||||
<div>
|
||||
<h1 class="home-hero-header__title">SignFlow</h1>
|
||||
<p class="home-hero-header__subtitle">Willkommen im eSign-Portal</p>
|
||||
<p class="home-hero-header__subtitle">@L("Index_Subtitle", "Willkommen im eSign-Portal")</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,7 +46,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/>
|
||||
</svg>
|
||||
Zur Umschlag-Übersicht
|
||||
@L("Index_ToEnvelopeOverview", "Zur Umschlag-Übersicht")
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
|
||||
</svg>
|
||||
@@ -54,20 +57,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
|
||||
</svg>
|
||||
Sicherer Zugang
|
||||
@L("Index_SecureAccess", "Sicherer Zugang")
|
||||
</div>
|
||||
<div class="home-feature-badge">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
|
||||
</svg>
|
||||
Digitale Unterschrift
|
||||
@L("Index_DigitalSignature", "Digitale Unterschrift")
|
||||
</div>
|
||||
<div class="home-feature-badge">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||
</svg>
|
||||
PDF-Export
|
||||
@L("Index_PdfExport", "PDF-Export")
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -80,10 +83,9 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private const string HomePageDescription =
|
||||
"Das digitale Unterschriftenportal ist eine Plattform, die entwickelt wurde, um Ihre Dokumente sicher zu unterschreiben und zu verwalten. " +
|
||||
"Mit seiner benutzerfreundlichen Oberfläche können Sie Ihre Dokumente schnell hochladen, die Unterschriftsprozesse verfolgen und Ihre digitalen Unterschriftenanwendungen einfach durchführen. " +
|
||||
"Dieses Portal beschleunigt Ihren Arbeitsablauf mit rechtlich gültigen Unterschriften und erhöht gleichzeitig die Sicherheit Ihrer Dokumente.";
|
||||
private string HomePageDescription => L(
|
||||
"HomePageDescription",
|
||||
"Das digitale Unterschriftenportal ist eine Plattform, die entwickelt wurde, um Ihre Dokumente sicher zu unterschreiben und zu verwalten. Mit seiner benutzerfreundlichen Oberfläche können Sie Ihre Dokumente schnell hochladen, die Unterschriftsprozesse verfolgen und Ihre digitalen Unterschriftenanwendungen einfach durchführen. Dieses Portal beschleunigt Ihren Arbeitsablauf mit rechtlich gültigen Unterschriften und erhöht gleichzeitig die Sicherheit Ihrer Dokumente.");
|
||||
|
||||
private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _appLogo;
|
||||
|
||||
@@ -99,4 +101,10 @@
|
||||
if (!firstRender) return;
|
||||
await JS.InvokeVoidAsync("receiverSignature.startTyped", "home-description", HomePageDescription, 15);
|
||||
}
|
||||
|
||||
string L(string key, string fallback)
|
||||
{
|
||||
var value = UiLocalizer[key];
|
||||
return value.ResourceNotFound ? fallback : value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
@page "/envelope/login/{EnvelopeKey}"
|
||||
@rendermode InteractiveWebAssembly
|
||||
@using EnvelopeGenerator.Server.Client.Services
|
||||
@using EnvelopeGenerator.Application.Resources
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager Navigation
|
||||
@inject IStringLocalizer<Resource> UiLocalizer
|
||||
|
||||
<div class="login-page-wrapper d-flex align-items-center justify-content-center min-vh-100">
|
||||
<div class="login-card card shadow border-0" style="max-width: 440px; width: 100%;">
|
||||
@@ -13,14 +16,14 @@
|
||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="mb-0 fw-semibold">Dokument öffnen</h5>
|
||||
<p class="mb-0 mt-1 opacity-75" style="font-size: 0.85rem;">Sicherer Zugang mit Zugangscode</p>
|
||||
<h5 class="mb-0 fw-semibold">@L("OpenDocument", "Dokument öffnen")</h5>
|
||||
<p class="mb-0 mt-1 opacity-75" style="font-size: 0.85rem;">@L("ReceiverLogin_Subtitle", "Sicherer Zugang mit Zugangscode")</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4">
|
||||
|
||||
<p class="text-muted mb-4" style="font-size: 0.875rem; line-height: 1.5;">
|
||||
Bitte geben Sie den Zugangscode ein, den Sie per E-Mail erhalten haben, um das Dokument sicher zu öffnen.
|
||||
@L("ReceiverLogin_Description", "Bitte geben Sie den Zugangscode ein, den Sie per E-Mail erhalten haben, um das Dokument sicher zu öffnen.")
|
||||
</p>
|
||||
|
||||
@if (LoginResult == EnvelopeLoginResult.NotFound)
|
||||
@@ -30,8 +33,8 @@
|
||||
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
|
||||
</svg>
|
||||
<div>
|
||||
<strong>Dokument nicht gefunden.</strong><br />
|
||||
<span style="font-size:0.85rem;">Der angegebene Zugangscode konnte keinem Dokument zugeordnet werden. Bitte prüfen Sie den Link in Ihrer E-Mail.</span>
|
||||
<strong>@L("ReceiverLogin_NotFound_Title", "Dokument nicht gefunden.")</strong><br />
|
||||
<span style="font-size:0.85rem;">@L("ReceiverLogin_NotFound_Body", "Der angegebene Zugangscode konnte keinem Dokument zugeordnet werden. Bitte prüfen Sie den Link in Ihrer E-Mail.")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -42,8 +45,8 @@
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z" />
|
||||
</svg>
|
||||
<div>
|
||||
<strong>Ungültiger Zugangscode.</strong><br />
|
||||
<span style="font-size:0.85rem;">Der eingegebene Code ist falsch. Bitte versuchen Sie es erneut.</span>
|
||||
<strong>@L("ReceiverLogin_InvalidCode_Title", "Ungültiger Zugangscode.")</strong><br />
|
||||
<span style="font-size:0.85rem;">@L("ReceiverLogin_InvalidCode_Body", "Der eingegebene Code ist falsch. Bitte versuchen Sie es erneut.")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -55,15 +58,15 @@
|
||||
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
||||
</svg>
|
||||
<div>
|
||||
<strong>Serverfehler.</strong><br />
|
||||
<span style="font-size:0.85rem;">Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.</span>
|
||||
<strong>@L("ServerError", "Serverfehler.")</strong><br />
|
||||
<span style="font-size:0.85rem;">@L("UnexpectedErrorTryAgainLater", "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-medium" for="login-access-code">
|
||||
Zugangscode
|
||||
@L("AccessCode", "Zugangscode")
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
@@ -75,7 +78,7 @@
|
||||
<input id="login-access-code"
|
||||
type="@(ShowCode ? "text" : "password")"
|
||||
class="form-control border-start-0 border-end-0 @(LoginResult == EnvelopeLoginResult.InvalidCode ? "is-invalid" : null)"
|
||||
placeholder="Zugangscode eingeben"
|
||||
placeholder="@L("ReceiverLogin_AccessCode_Placeholder", "Zugangscode eingeben")"
|
||||
@bind="AccessCode"
|
||||
@bind:event="oninput"
|
||||
@onkeydown="OnKeyDownAsync"
|
||||
@@ -113,21 +116,21 @@
|
||||
@if (IsLoading)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||
<span>Überprüfen …</span>
|
||||
<span>@L("VerifyInProgress", "Überprüfen …")</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-2" viewBox="0 0 16 16">
|
||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z" />
|
||||
</svg>
|
||||
<span>Dokument öffnen</span>
|
||||
<span>@L("OpenDocument", "Dokument öffnen")</span>
|
||||
}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-footer text-center text-muted py-3 border-0 bg-transparent" style="font-size: 0.78rem;">
|
||||
Bei Problemen wenden Sie sich bitte an den Absender des Dokuments.
|
||||
@L("ReceiverLogin_FooterHelp", "Bei Problemen wenden Sie sich bitte an den Absender des Dokuments.")
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -167,5 +170,11 @@
|
||||
IsLoading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
string L(string key, string fallback)
|
||||
{
|
||||
var value = UiLocalizer[key];
|
||||
return value.ResourceNotFound ? fallback : value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
@page "/sender/login"
|
||||
@rendermode InteractiveWebAssembly
|
||||
@using EnvelopeGenerator.Server.Client.Services
|
||||
@using EnvelopeGenerator.Application.Resources
|
||||
@using Microsoft.Extensions.Localization
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager Navigation
|
||||
@inject IStringLocalizer<Resource> UiLocalizer
|
||||
|
||||
<div class="login-page-wrapper d-flex align-items-center justify-content-center min-vh-100">
|
||||
<div class="login-card card shadow border-0" style="max-width: 440px; width: 100%;">
|
||||
@@ -14,14 +17,14 @@
|
||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="mb-0 fw-semibold">Sender Anmeldung</h5>
|
||||
<p class="mb-0 mt-1 opacity-75" style="font-size: 0.85rem;">Sicherer Zugang zum Sender-Dashboard</p>
|
||||
<h5 class="mb-0 fw-semibold">@L("SenderLogin_Title", "Sender Anmeldung")</h5>
|
||||
<p class="mb-0 mt-1 opacity-75" style="font-size: 0.85rem;">@L("SenderLogin_Subtitle", "Sicherer Zugang zum Sender-Dashboard")</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4">
|
||||
|
||||
<p class="text-muted mb-4" style="font-size: 0.875rem; line-height: 1.5;">
|
||||
Bitte melden Sie sich mit Ihren Zugangsdaten an, um auf das Sender-Dashboard zuzugreifen.
|
||||
@L("SenderLogin_Description", "Bitte melden Sie sich mit Ihren Zugangsdaten an, um auf das Sender-Dashboard zuzugreifen.")
|
||||
</p>
|
||||
|
||||
@if (LoginResult == SenderLoginResult.InvalidCredentials) {
|
||||
@@ -30,8 +33,8 @@
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
|
||||
</svg>
|
||||
<div>
|
||||
<strong>Ungültige Anmeldedaten.</strong><br />
|
||||
<span style="font-size:0.85rem;">Benutzername oder Passwort ist falsch. Bitte versuchen Sie es erneut.</span>
|
||||
<strong>@L("SenderLogin_InvalidCredentials_Title", "Ungültige Anmeldedaten.")</strong><br />
|
||||
<span style="font-size:0.85rem;">@L("SenderLogin_InvalidCredentials_Body", "Benutzername oder Passwort ist falsch. Bitte versuchen Sie es erneut.")</span>
|
||||
</div>
|
||||
</div>
|
||||
} else if (LoginResult == SenderLoginResult.Error) {
|
||||
@@ -41,15 +44,15 @@
|
||||
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
|
||||
</svg>
|
||||
<div>
|
||||
<strong>Serverfehler.</strong><br />
|
||||
<span style="font-size:0.85rem;">Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.</span>
|
||||
<strong>@L("ServerError", "Serverfehler.")</strong><br />
|
||||
<span style="font-size:0.85rem;">@L("UnexpectedErrorTryAgainLater", "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium" for="login-username">
|
||||
Benutzername
|
||||
@L("UserName", "Benutzername")
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
@@ -61,7 +64,7 @@
|
||||
<input id="login-username"
|
||||
type="text"
|
||||
class="form-control @(LoginResult == SenderLoginResult.InvalidCredentials ? "is-invalid" : null)"
|
||||
placeholder="Benutzername eingeben"
|
||||
placeholder="@L("SenderLogin_Username_Placeholder", "Benutzername eingeben")"
|
||||
@bind="Username"
|
||||
@bind:event="oninput"
|
||||
@onkeydown="OnKeyDownAsync"
|
||||
@@ -72,7 +75,7 @@
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-medium" for="login-password">
|
||||
Passwort
|
||||
@L("Password", "Passwort")
|
||||
<span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
@@ -84,7 +87,7 @@
|
||||
<input id="login-password"
|
||||
type="@(ShowPassword ? "text" : "password")"
|
||||
class="form-control border-start-0 border-end-0 @(LoginResult == SenderLoginResult.InvalidCredentials ? "is-invalid" : null)"
|
||||
placeholder="Passwort eingeben"
|
||||
placeholder="@L("SenderLogin_Password_Placeholder", "Passwort eingeben")"
|
||||
@bind="Password"
|
||||
@bind:event="oninput"
|
||||
@onkeydown="OnKeyDownAsync"
|
||||
@@ -118,20 +121,20 @@
|
||||
disabled="@(IsLoading || string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password))">
|
||||
@if (IsLoading) {
|
||||
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||
<span>Anmelden …</span>
|
||||
<span>@L("LoginInProgress", "Anmelden …")</span>
|
||||
} else {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-2" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M10 3.5a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 1 1 0v2A1.5 1.5 0 0 1 9.5 14h-8A1.5 1.5 0 0 1 0 12.5v-9A1.5 1.5 0 0 1 1.5 2h8A1.5 1.5 0 0 1 11 3.5v2a.5.5 0 0 1-1 0v-2z"/>
|
||||
<path fill-rule="evenodd" d="M4.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H14.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3z"/>
|
||||
</svg>
|
||||
<span>Anmelden</span>
|
||||
<span>@L("Login", "Anmelden")</span>
|
||||
}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-footer text-center text-muted py-3 border-0 bg-transparent" style="font-size: 0.78rem;">
|
||||
Bei Problemen wenden Sie sich bitte an den Administrator.
|
||||
@L("SenderLogin_FooterHelp", "Bei Problemen wenden Sie sich bitte an den Administrator.")
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -167,4 +170,10 @@
|
||||
IsLoading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
string L(string key, string fallback)
|
||||
{
|
||||
var value = UiLocalizer[key];
|
||||
return value.ResourceNotFound ? fallback : value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,17 @@ using DevExpress.Blazor.Reporting;
|
||||
using DevExpress.XtraReports.Web.Extensions;
|
||||
using DevExpress.DataAccess.Web;
|
||||
using DevExpress.XtraReports.Services;
|
||||
using System.Globalization;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
|
||||
var initialCulture = await ResolveInitialCultureAsync(builder.HostEnvironment.BaseAddress);
|
||||
var cultureInfo = new CultureInfo(initialCulture);
|
||||
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
|
||||
|
||||
builder.Services.AddLocalization();
|
||||
|
||||
// Named HttpClient for API calls (both for services and DevExpress components)
|
||||
builder.Services.AddHttpClient("EnvelopeGenerator.Server", client =>
|
||||
{
|
||||
@@ -67,3 +75,23 @@ ReportStorageWebExtension.RegisterExtensionGlobal(new InMemoryReportStorageWebEx
|
||||
var host = builder.Build();
|
||||
await FontLoader.LoadFonts(host.Services.GetRequiredService<IHttpClientFactory>(), new List<string> { "opensans.ttf" });
|
||||
await host.RunAsync();
|
||||
|
||||
static async Task<string> ResolveInitialCultureAsync(string baseAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var http = new HttpClient { BaseAddress = new Uri(baseAddress) };
|
||||
var response = await http.GetAsync("/api/Localization/lang");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return "de-DE";
|
||||
|
||||
var value = await response.Content.ReadAsStringAsync();
|
||||
var culture = value.Trim().Trim('"');
|
||||
return string.IsNullOrWhiteSpace(culture) ? "de-DE" : culture;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "de-DE";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user