generated from Seekra/repository-template
Add more Languages and switch button #70
@@ -0,0 +1,153 @@
|
||||
<!--
|
||||
Copyright 2026 Seekra
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { loadLanguage } from '@/i18n';
|
||||
|
||||
const { locale } = useI18n();
|
||||
|
||||
const isOpen = ref(false);
|
||||
|
||||
const languages = [
|
||||
|
jakob.scheid marked this conversation as resolved
Outdated
|
||||
{ code: 'en', label: 'English', flag: '🇬🇧' },
|
||||
|
jakob.scheid marked this conversation as resolved
Outdated
jakob.scheid
commented
You do not need lines You do not need lines `const currentLanguage = …` if you simply write
```
const languages = {
'en': {
label: 'English',
flag: '🇬🇧'
},
…
}
```
|
||||
{ code: 'de', label: 'Deutsch', flag: '🇩🇪' },
|
||||
{ code: 'fr', label: 'Français', flag: '🇫🇷' },
|
||||
{ code: 'es', label: 'Español', flag: '🇪🇸' },
|
||||
{ code: 'it', label: 'Italiano', flag: '🇮🇹' },
|
||||
{ code: 'pt', label: 'Português', flag: '🇵🇹' },
|
||||
];
|
||||
|
||||
const currentLanguage = computed(
|
||||
() => languages.find(l => l.code === locale.value) ?? languages[0]
|
||||
);
|
||||
|
||||
async function selectLanguage(code) {
|
||||
|
jakob.scheid marked this conversation as resolved
Outdated
jakob.scheid
commented
I think an extra function that simply calls another function is not necessary. I think an extra function that simply calls another function is not necessary.
|
||||
await loadLanguage(code);
|
||||
localStorage.setItem('locale', code);
|
||||
document.documentElement.lang = code;
|
||||
document.documentElement.dir = code === 'ar' ? 'rtl' : 'ltr';
|
||||
|
jakob.scheid marked this conversation as resolved
Outdated
jakob.scheid
commented
Consider that Arabic isn't the only language written from right to left. Consider that Arabic isn't the only language written from right to left.
|
||||
isOpen.value = false;
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
isOpen.value = !isOpen.value;
|
||||
}
|
||||
|
||||
function closeOnBlur() {
|
||||
// Kleines Delay damit Click auf Option noch registriert wird
|
||||
|
jakob.scheid marked this conversation as resolved
Outdated
jakob.scheid
commented
I ask you to speak English. I ask you to speak English.
|
||||
setTimeout(() => { isOpen.value = false; }, 150);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="language-switch" @blur="closeOnBlur" tabindex="-1">
|
||||
<button
|
||||
class="language-button button"
|
||||
@click="toggle"
|
||||
:aria-expanded="isOpen"
|
||||
aria-haspopup="listbox"
|
||||
>
|
||||
<span class="flag">{{ currentLanguage.flag }}</span>
|
||||
<span class="lang-code">{{ currentLanguage.code.toUpperCase() }}</span>
|
||||
<span class="chevron" :class="{ open: isOpen }">▾</span>
|
||||
</button>
|
||||
|
||||
<ul v-if="isOpen" class="language-dropdown" role="listbox">
|
||||
<li
|
||||
v-for="lang in languages"
|
||||
:key="lang.code"
|
||||
role="option"
|
||||
:aria-selected="lang.code === locale"
|
||||
:class="{ active: lang.code === locale }"
|
||||
@click="selectLanguage(lang.code)"
|
||||
>
|
||||
<span class="flag">{{ lang.flag }}</span>
|
||||
<span class="lang-label">{{ lang.label }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.language-switch {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.language-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: none;
|
||||
border: 1px solid var(--dark);
|
||||
border-radius: 6px;
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.language-button:hover {
|
||||
background-color: var(--light-d-1);
|
||||
}
|
||||
|
||||
.chevron {
|
||||
font-size: 0.75rem;
|
||||
transition: transform 0.2s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.chevron.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(100% + 6px);
|
||||
background-color: var(--light-bg);
|
||||
border: 1px solid var(--light-d-1);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 6px 0;
|
||||
min-width: 160px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.language-dropdown li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.language-dropdown li:hover {
|
||||
background-color: var(--light-d-1);
|
||||
}
|
||||
|
||||
.language-dropdown li.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.flag {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<!--
|
||||
Copyright 2026 Seekra
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script setup>
|
||||
import ColorSchemeButton from '@/features/colorScheme/components/ColorSchemeButton.vue';
|
||||
import LanguageSwitchButton from '@/features/language/components/LanguageSwitchButton.vue';
|
||||
import logo from '@/assets/logo.svg';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="global-nav">
|
||||
<RouterLink to="/" class="link button link">
|
||||
<img :src="logo" alt="Seekra" class="nav-logo" />
|
||||
</RouterLink>
|
||||
<ul class="right-links">
|
||||
<li>
|
||||
<LanguageSwitchButton />
|
||||
</li>
|
||||
<li>
|
||||
<ColorSchemeButton />
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.global-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 40px;
|
||||
}
|
||||
|
||||
.global-nav .right-links {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.global-nav .right-links a {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.global-nav .right-links a:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
height: 24px;
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user
Please do not use hard-coded values. All strings should be in the locale files.