From ba9f233993d0a373b0afc9c28059e9aac1feea81 Mon Sep 17 00:00:00 2001 From: TekH Date: Wed, 17 Jun 2026 16:57:39 +0200 Subject: [PATCH] Add Language Selector component to footer Introduced a Language Selector component (`LanguageSelector.razor`) to enable users to switch between German, English, and French. The component includes a dropdown UI with flag icons and language labels, along with logic for toggling the dropdown and changing the app's culture. Injected `IJSRuntime`, `NavigationManager`, and `CultureService` into the component to handle culture changes and navigation updates. Updated `MainLayout.razor` to include the Language Selector in the footer. Refactored footer layout in `MainLayout.razor.css` to use flexbox for better alignment and spacing. Added styles for the Language Selector in `app.css` to ensure a clean and responsive design. Integrated the `flag-icons` library in `index.html` to display flag icons for language representation. --- .../Shared/LanguageSelector.razor | 62 ++++++++++++++++ .../Shared/LanguageSelector.razor.css | 0 .../Shared/MainLayout.razor | 9 ++- .../Shared/MainLayout.razor.css | 15 ++++ .../wwwroot/css/app.css | 73 ++++++++++++++++++- .../wwwroot/index.html | 1 + 6 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 EnvelopeGenerator.ReceiverUI/Shared/LanguageSelector.razor create mode 100644 EnvelopeGenerator.ReceiverUI/Shared/LanguageSelector.razor.css diff --git a/EnvelopeGenerator.ReceiverUI/Shared/LanguageSelector.razor b/EnvelopeGenerator.ReceiverUI/Shared/LanguageSelector.razor new file mode 100644 index 00000000..a24a4f4e --- /dev/null +++ b/EnvelopeGenerator.ReceiverUI/Shared/LanguageSelector.razor @@ -0,0 +1,62 @@ +@using System.Globalization +@using EnvelopeGenerator.ReceiverUI.Services +@inject IJSRuntime JSRuntime +@inject NavigationManager Navigation +@inject CultureService CultureService + +
+ + + @if (isOpen) + { +
+ + + +
+ } +
+ +@code { + private bool isOpen = false; + private string CurrentCulture => CultureInfo.CurrentCulture.Name; + + private void ToggleDropdown() + { + isOpen = !isOpen; + } + + private async Task ChangeLanguageAsync(string culture) + { + if (CultureInfo.CurrentCulture.Name != culture) + { + await CultureService.SetCultureAsync(culture); + Navigation.NavigateTo(Navigation.Uri, forceLoad: true); + } + + isOpen = false; + } + + private string GetFlagCode(string culture) + { + return culture switch + { + "de-DE" => "de", + "en-US" => "us", + "fr-FR" => "fr", + _ => "de" + }; + } +} diff --git a/EnvelopeGenerator.ReceiverUI/Shared/LanguageSelector.razor.css b/EnvelopeGenerator.ReceiverUI/Shared/LanguageSelector.razor.css new file mode 100644 index 00000000..e69de29b diff --git a/EnvelopeGenerator.ReceiverUI/Shared/MainLayout.razor b/EnvelopeGenerator.ReceiverUI/Shared/MainLayout.razor index e667fbac..873ea018 100644 --- a/EnvelopeGenerator.ReceiverUI/Shared/MainLayout.razor +++ b/EnvelopeGenerator.ReceiverUI/Shared/MainLayout.razor @@ -8,9 +8,12 @@ diff --git a/EnvelopeGenerator.ReceiverUI/Shared/MainLayout.razor.css b/EnvelopeGenerator.ReceiverUI/Shared/MainLayout.razor.css index d662d7ea..ef51f3a9 100644 --- a/EnvelopeGenerator.ReceiverUI/Shared/MainLayout.razor.css +++ b/EnvelopeGenerator.ReceiverUI/Shared/MainLayout.razor.css @@ -15,3 +15,18 @@ article { padding: 0 !important; margin: 0 !important; } + +.receiver-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + gap: 1rem; +} + +.receiver-footer__content { + display: flex; + align-items: center; + gap: 0.5rem; +} + diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/app.css b/EnvelopeGenerator.ReceiverUI/wwwroot/css/app.css index 9f967169..48de5029 100644 --- a/EnvelopeGenerator.ReceiverUI/wwwroot/css/app.css +++ b/EnvelopeGenerator.ReceiverUI/wwwroot/css/app.css @@ -365,4 +365,75 @@ article { .receiver-footer__sep { opacity: 0.4; -} \ No newline at end of file +} + +/* ── Language Selector (Footer) ──────────────────────────────────────────── */ +.language-selector { + position: relative; + display: inline-block; +} + +.language-selector__trigger { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.25rem 0.5rem; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + color: rgba(255, 255, 255, 0.9); + cursor: pointer; + font-size: 1rem; + transition: all 0.2s ease; +} + +.language-selector__trigger:hover { + background: rgba(255, 255, 255, 0.15); + border-color: rgba(255, 255, 255, 0.3); +} + +.language-selector__arrow { + font-size: 0.6rem; + transition: transform 0.2s ease; + opacity: 0.7; +} + +.language-selector__dropdown { + position: absolute; + bottom: calc(100% + 0.5rem); + right: 0; + background: white; + border: 1px solid #ddd; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + min-width: 160px; + z-index: 1000; + overflow: hidden; +} + +.language-selector__option { + display: flex; + align-items: center; + gap: 0.75rem; + width: 100%; + padding: 0.65rem 1rem; + background: white; + border: none; + color: #333; + cursor: pointer; + font-size: 0.9rem; + transition: background-color 0.2s ease; + text-align: left; +} + +.language-selector__option:hover { + background-color: #f5f5f5; +} + +.language-selector__option .fi { + font-size: 1.25rem; +} + +.language-selector__option span:last-child { + font-weight: 500; +} diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/index.html b/EnvelopeGenerator.ReceiverUI/wwwroot/index.html index ce8ae2fe..ec48b8fa 100644 --- a/EnvelopeGenerator.ReceiverUI/wwwroot/index.html +++ b/EnvelopeGenerator.ReceiverUI/wwwroot/index.html @@ -12,6 +12,7 @@ +