feat(localization): expand language support and enhance UI for user settings

- Added support for additional UI locales including Cebuano and Spanish, improving accessibility for a broader user base.
- Updated language selection components in the AppHeader and SettingsWidget to reflect new language options, enhancing user experience.
- Enhanced localization of various UI elements across components, ensuring consistent language representation and improved user engagement.
- Implemented logic to synchronize user language preferences with backend settings, providing a seamless experience when changing languages.
This commit is contained in:
Torsten Schulz (local)
2026-04-02 07:54:44 +02:00
parent 6e1fe6f3a8
commit c50a6b3c35
72 changed files with 1792 additions and 343 deletions

View File

@@ -5,21 +5,34 @@
<div class="logo"><img src="/images/logos/logo.png" alt="YourPart" /></div>
<div class="brand-copy">
<strong>YourPart</strong>
<span>Community-Plattform</span>
<span>{{ $t('appShell.header.tagline') }}</span>
</div>
</div>
<div class="header-meta">
<div class="header-meta__context">
<span class="header-pill">Beta</span>
<span class="header-pill">{{ $t('appShell.header.beta') }}</span>
<label class="header-lang">
<span class="header-lang__label">{{ $t('appShell.header.language') }}</span>
<select
class="header-lang__select"
:aria-label="$t('appShell.header.language')"
:value="language"
@change="onUiLanguageChange($event.target.value)"
>
<option v-for="opt in uiLocaleOptions" :key="opt.value" :value="opt.value">
{{ $t(opt.labelTr) }}
</option>
</select>
</label>
</div>
<div class="connection-status" v-if="isLoggedIn">
<div class="status-indicator" :class="backendStatusClass">
<span class="status-dot"></span>
<span class="status-text">Backend</span>
<span class="status-text">{{ $t('appShell.header.backend') }}</span>
</div>
<div class="status-indicator" :class="daemonStatusClass">
<span class="status-dot"></span>
<span class="status-text">Daemon</span>
<span class="status-text">{{ $t('appShell.header.daemon') }}</span>
</div>
</div>
</div>
@@ -29,11 +42,22 @@
<script>
import { mapGetters } from 'vuex';
import apiClient from '@/utils/axios.js';
export default {
name: 'AppHeader',
data() {
return {
uiLocaleOptions: [
{ value: 'de', labelTr: 'settings.personal.language.de' },
{ value: 'en', labelTr: 'settings.personal.language.en' },
{ value: 'ceb', labelTr: 'settings.personal.language.ceb' },
{ value: 'es', labelTr: 'settings.personal.language.es' },
],
};
},
computed: {
...mapGetters(['isLoggedIn', 'connectionStatus', 'daemonConnectionStatus']),
...mapGetters(['isLoggedIn', 'user', 'language', 'connectionStatus', 'daemonConnectionStatus']),
backendStatusClass() {
return {
'status-connected': this.connectionStatus === 'connected',
@@ -50,7 +74,40 @@ export default {
'status-error': this.daemonConnectionStatus === 'error'
};
}
}
},
methods: {
async onUiLanguageChange(code) {
const supported = ['de', 'en', 'ceb', 'es'];
if (!supported.includes(code)) {
return;
}
await this.$store.dispatch('setLanguage', code);
if (!this.isLoggedIn || !this.user?.id) {
return;
}
try {
const { data } = await apiClient.post('/api/settings/filter', {
userid: this.user.id,
type: 'personal',
});
const langRow = data.find((s) => s.name === 'language');
if (!langRow?.options?.length) {
return;
}
const opt = langRow.options.find((o) => o.value === code);
if (!opt) {
return;
}
await apiClient.post('/api/settings/update', {
userid: this.user.id,
settingId: langRow.id,
value: opt.id,
});
} catch (err) {
console.warn('AppHeader: profile language could not be synced', err);
}
},
},
};
</script>
@@ -144,6 +201,38 @@ export default {
color: #8a5411;
}
.header-lang {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 0;
font-size: 0.72rem;
color: rgba(95, 75, 57, 0.85);
}
.header-lang__label {
font-weight: 600;
white-space: nowrap;
}
.header-lang__select {
min-width: 7.5rem;
max-width: 11rem;
padding: 4px 8px;
border-radius: 8px;
border: 1px solid rgba(93, 64, 55, 0.18);
background: rgba(255, 255, 255, 0.85);
color: #3a2a1b;
font-size: 0.72rem;
font-weight: 600;
cursor: pointer;
}
.header-lang__select:focus {
outline: 2px solid rgba(248, 162, 43, 0.45);
outline-offset: 1px;
}
.connection-status {
display: flex;
align-items: center;