Add multilingual support with language selector
Introduced a `LanguageControls` component for switching between languages (`de-DE`, `en-US`, `fr-FR`) and integrated a new `LocalizationService` to manage language preferences. Updated `LocalizationController` to handle language persistence via cookies and support multiple cultures. Enhanced the footer layout to include the language selector alongside theme controls. Added `flag-icons` library for displaying country flags and configured request localization in `Program.cs`. Styled the language selector dropdown and improved sender page visuals with updated CSS. Defaulted language handling to `de-DE` for fallback scenarios. Cleaned up redundant code in `LocalizationController`.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="stylesheet" href="css/envelope-viewer.css" />
|
||||
<link rel="stylesheet" href="EnvelopeGenerator.Server.styles.css" />
|
||||
|
||||
@@ -527,6 +527,7 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@* ── PDF Vorschau Popup ────────────────────────────────────────────────── *@
|
||||
|
||||
@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using EnvelopeGenerator.Application.Resources;
|
||||
|
||||
namespace EnvelopeGenerator.Server.Controllers;
|
||||
|
||||
@@ -20,7 +19,6 @@ public class LocalizationController : ControllerBase
|
||||
|
||||
private readonly ILogger<LocalizationController> _logger;
|
||||
private readonly IStringLocalizer<Resource> _mLocalizer;
|
||||
private readonly IStringLocalizer<Resource> _localizer;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
/// <summary>
|
||||
@@ -32,14 +30,12 @@ public class LocalizationController : ControllerBase
|
||||
/// <param name="_modelLocalizer">Lokalisierungsdienst für Modelle.</param>
|
||||
public LocalizationController(
|
||||
ILogger<LocalizationController> logger,
|
||||
IStringLocalizer<Resource> localizer,
|
||||
IMemoryCache memoryCache,
|
||||
IStringLocalizer<Resource> _modelLocalizer)
|
||||
IStringLocalizer<Resource> modelLocalizer)
|
||||
{
|
||||
_logger = logger;
|
||||
_localizer = localizer;
|
||||
_cache = memoryCache;
|
||||
_mLocalizer = _modelLocalizer;
|
||||
_mLocalizer = modelLocalizer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -337,6 +337,7 @@ try
|
||||
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.UserPreferencesService>();
|
||||
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.ThemeService>();
|
||||
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.CustomImagesService>();
|
||||
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.LocalizationService>();
|
||||
|
||||
// Business Services (Server specific)
|
||||
builder.Services.AddScoped<DocumentService>();
|
||||
@@ -410,9 +411,14 @@ try
|
||||
// Set CORS policy
|
||||
app.UseCors("AllowSpecificOriginsPolicy");
|
||||
|
||||
// Localization (fixed to German for now; persisted via cookie)
|
||||
// Localization (default German; can be switched via cookie)
|
||||
var defaultCulture = new CultureInfo("de-DE");
|
||||
IList<CultureInfo> list = [defaultCulture];
|
||||
IList<CultureInfo> list =
|
||||
[
|
||||
defaultCulture,
|
||||
new CultureInfo("en-US"),
|
||||
new CultureInfo("fr-FR")
|
||||
];
|
||||
var requestLocalizationOptions = new RequestLocalizationOptions
|
||||
{
|
||||
DefaultRequestCulture = new RequestCulture(defaultCulture),
|
||||
@@ -420,6 +426,7 @@ try
|
||||
SupportedUICultures = list,
|
||||
RequestCultureProviders = [new CookieRequestCultureProvider()]
|
||||
};
|
||||
requestLocalizationOptions.ApplyCurrentCultureToResponseHeaders = true;
|
||||
app.UseRequestLocalization(requestLocalizationOptions);
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
@@ -404,6 +404,89 @@ article {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.receiver-footer__right {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sf-lang-combo {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sf-lang-trigger {
|
||||
width: 28px;
|
||||
height: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.28);
|
||||
border-radius: 5px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-lang-trigger:hover {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.sf-lang-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 64px;
|
||||
padding: 6px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
background: rgba(29, 38, 55, 0.98);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
|
||||
z-index: 110;
|
||||
}
|
||||
|
||||
.sf-lang-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: default;
|
||||
z-index: 105;
|
||||
}
|
||||
|
||||
.sf-lang-option {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-lang-option:hover,
|
||||
.sf-lang-option--active {
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
|
||||
.sf-lang-trigger .fi,
|
||||
.sf-lang-option .fi {
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.25) inset;
|
||||
}
|
||||
|
||||
/* ── Footer: Theme Slider ────────────────────────────────────────────────── */
|
||||
/* 🌙 blazing-dark ──◇──·──◇── purple ☀️ */
|
||||
/* Left group: copyright | datenschutz → Right group: slider */
|
||||
@@ -613,4 +696,4 @@ article {
|
||||
|
||||
[data-sf-theme="blazing-dark"] .login-card .text-muted {
|
||||
color: rgba(255, 255, 255, 0.50) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
.sender-grid-container {
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
backdrop-filter: blur(20px);
|
||||
@@ -690,6 +691,7 @@
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
|
||||
[data-sf-theme="blazing-dark"] .sender-grid-container::before {
|
||||
background: linear-gradient(90deg, #6366f1 0%, #4338ca 100%);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user