refactor language selector into separate component
Extract language switching functionality from UserHeader into a new LanguageSelector component. In mobile view, the selector appears in the top bar next to the hamburger menu. In desktop view, it appears in the navigation bar to the left of the UserHeader. - Create LanguageSelector component with local element bindings - Update Header component with language_selector_mobile and language_selector_desktop slots - Remove language selector code from UserHeader - Update recipe and main layouts to use new component - Hide desktop language selector inside mobile hamburger menu
This commit is contained in:
163
src/lib/components/LanguageSelector.svelte
Normal file
163
src/lib/components/LanguageSelector.svelte
Normal file
@@ -0,0 +1,163 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let currentLang = $state('de');
|
||||
let currentPath = $state('');
|
||||
let langButton: HTMLButtonElement;
|
||||
let langOptions: HTMLDivElement;
|
||||
|
||||
$effect(() => {
|
||||
// Update current language and path when page changes
|
||||
if (typeof window !== 'undefined') {
|
||||
const path = window.location.pathname;
|
||||
currentPath = path;
|
||||
if (path.startsWith('/recipes')) {
|
||||
currentLang = 'en';
|
||||
} else if (path.startsWith('/rezepte')) {
|
||||
currentLang = 'de';
|
||||
} else if (path === '/') {
|
||||
// On main page, read from localStorage
|
||||
const preferredLanguage = localStorage.getItem('preferredLanguage');
|
||||
currentLang = preferredLanguage === 'en' ? 'en' : 'de';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function toggle_language_options(){
|
||||
if (langOptions) {
|
||||
langOptions.hidden = !langOptions.hidden;
|
||||
}
|
||||
}
|
||||
|
||||
async function switchLanguage(lang: 'de' | 'en') {
|
||||
// Update the current language state immediately
|
||||
currentLang = lang;
|
||||
|
||||
// Store preference
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('preferredLanguage', lang);
|
||||
}
|
||||
|
||||
// Get the current path directly from window
|
||||
const path = typeof window !== 'undefined' ? window.location.pathname : currentPath;
|
||||
|
||||
// If on main page, dispatch event instead of reloading
|
||||
if (path === '/') {
|
||||
window.dispatchEvent(new CustomEvent('languagechange', { detail: { lang } }));
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have recipe translation data from store, use the correct short names
|
||||
const recipeData = $recipeTranslationStore;
|
||||
if (recipeData) {
|
||||
if (lang === 'en' && recipeData.englishShortName) {
|
||||
await goto(`/recipes/${recipeData.englishShortName}`);
|
||||
return;
|
||||
} else if (lang === 'de' && recipeData.germanShortName) {
|
||||
await goto(`/rezepte/${recipeData.germanShortName}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert current path to target language (for non-recipe pages)
|
||||
let newPath = path;
|
||||
if (lang === 'en' && path.startsWith('/rezepte')) {
|
||||
newPath = path.replace('/rezepte', '/recipes');
|
||||
} else if (lang === 'de' && path.startsWith('/recipes')) {
|
||||
newPath = path.replace('/recipes', '/rezepte');
|
||||
} else if (!path.startsWith('/rezepte') && !path.startsWith('/recipes')) {
|
||||
// On other pages (glaube, cospend, etc), go to recipe home
|
||||
newPath = lang === 'en' ? '/recipes' : '/rezepte';
|
||||
}
|
||||
|
||||
await goto(newPath);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if(langButton && !langButton.contains(e.target as Node)){
|
||||
if (langOptions) langOptions.hidden = true;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClick);
|
||||
};
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.language-selector{
|
||||
position: relative;
|
||||
}
|
||||
.language-button{
|
||||
width: auto;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
background-color: var(--nord3);
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 100ms;
|
||||
border: none;
|
||||
}
|
||||
.language-button:hover{
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
.language-options{
|
||||
--bg_color: var(--nord3);
|
||||
box-sizing: border-box;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(100% + 10px);
|
||||
background-color: var(--bg_color);
|
||||
width: 10ch;
|
||||
padding: 0.5rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
.language-options button{
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
text-align: left;
|
||||
transition: background-color 100ms;
|
||||
}
|
||||
.language-options button:hover{
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
.language-options button.active{
|
||||
background-color: var(--nord14);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="language-selector">
|
||||
<button bind:this={langButton} onclick={toggle_language_options} class="language-button">
|
||||
{currentLang.toUpperCase()}
|
||||
</button>
|
||||
<div bind:this={langOptions} class="language-options" hidden>
|
||||
<button
|
||||
class:active={currentLang === 'de'}
|
||||
onclick={() => switchLanguage('de')}
|
||||
>
|
||||
DE
|
||||
</button>
|
||||
<button
|
||||
class:active={currentLang === 'en'}
|
||||
onclick={() => switchLanguage('en')}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user