diff --git a/backend/services/authService.js b/backend/services/authService.js index 024f349..78af60a 100644 --- a/backend/services/authService.js +++ b/backend/services/authService.js @@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import User from '../models/community/user.js'; import UserParam from '../models/community/user_param.js'; import UserParamType from '../models/type/user_param.js'; +import UserParamValue from '../models/type/user_param_value.js'; import { sendAccountActivationEmail, sendPasswordResetEmail } from './emailService.js'; import { sequelize } from '../utils/sequelize.js'; import { Op } from 'sequelize'; @@ -143,6 +144,16 @@ export const loginUser = async ({ username, password }) => { const mappedParams = params.map(param => { return { 'name': param.paramType.description, 'value': param.value }; }); + const uiLocaleCodes = ['de', 'en', 'ceb', 'es']; + const langEntry = mappedParams.find((p) => p.name === 'language'); + if (langEntry?.value && !uiLocaleCodes.includes(langEntry.value)) { + const idNum = parseInt(langEntry.value, 10); + const lookupId = Number.isNaN(idNum) ? langEntry.value : idNum; + const upv = await UserParamValue.findOne({ where: { id: lookupId } }); + if (upv?.value && uiLocaleCodes.includes(upv.value)) { + langEntry.value = upv.value; + } + } console.log('return user'); return { id: user.hashedId, diff --git a/backend/utils/initializeTypes.js b/backend/utils/initializeTypes.js index 5ca2db2..a4e15c1 100644 --- a/backend/utils/initializeTypes.js +++ b/backend/utils/initializeTypes.js @@ -67,7 +67,7 @@ const initializeTypes = async () => { } const valuesList = { gender: ['male', 'female', 'transfemale', 'transmale', 'nonbinary'], - language: ['de', 'en'], + language: ['de', 'en', 'ceb', 'es'], eyecolor: ['blue', 'green', 'brown', 'black', 'grey', 'hazel', 'amber', 'red', 'other'], haircolor: ['black', 'brown', 'blonde', 'red', 'grey', 'white', 'other'], hairlength: ['short', 'medium', 'long', 'bald', 'other'], diff --git a/backend/utils/initializeWidgetTypes.js b/backend/utils/initializeWidgetTypes.js index 5a4720b..72aa8e3 100644 --- a/backend/utils/initializeWidgetTypes.js +++ b/backend/utils/initializeWidgetTypes.js @@ -1,5 +1,9 @@ import WidgetType from '../models/type/widget_type.js'; +/** + * Default rows for type.widget_type. Labels/descriptions are German for DB/admin readability; + * the web app maps known `endpoint` values to localized strings (see LoggedInView / home.dashboard.widgetLabels). + */ const DEFAULT_WIDGET_TYPES = [ { label: 'Termine', endpoint: '/api/termine', description: 'Bevorstehende Termine', orderId: 1 }, { label: 'Falukant', endpoint: '/api/falukant/dashboard-widget', description: 'Charakter, Geld, Nachrichten, Kinder', orderId: 2 }, diff --git a/frontend/src/components/AppFooter.vue b/frontend/src/components/AppFooter.vue index 612932e..8a3f3e4 100644 --- a/frontend/src/components/AppFooter.vue +++ b/frontend/src/components/AppFooter.vue @@ -4,10 +4,12 @@ @@ -23,7 +25,7 @@ {{ dialog.dialog.isTitleTranslated ? $t(dialog.dialog.localTitle) : dialog.dialog.localTitle }} - System bereit + {{ $t('appShell.footer.systemReady') }}
@@ -72,7 +74,7 @@ export default { }, // Daemon WebSocket deaktiviert - diese Funktionen sind nicht mehr verfügbar async showFalukantDaemonStatus() { - showInfo(this, 'Der Systemstatus ist in dieser Ansicht derzeit nicht direkt verfügbar.'); + showInfo(this, this.$t('appShell.footer.systemStatusUnavailable')); }, handleDaemonMessage() { // Status-Events werden hier bewusst nicht verarbeitet. diff --git a/frontend/src/components/AppHeader.vue b/frontend/src/components/AppHeader.vue index 8a0d789..835bfb1 100644 --- a/frontend/src/components/AppHeader.vue +++ b/frontend/src/components/AppHeader.vue @@ -5,21 +5,34 @@
YourPart - Community-Plattform + {{ $t('appShell.header.tagline') }}
- Beta + {{ $t('appShell.header.beta') }} +
- Backend + {{ $t('appShell.header.backend') }}
- Daemon + {{ $t('appShell.header.daemon') }}
@@ -29,11 +42,22 @@ @@ -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; diff --git a/frontend/src/components/DashboardWidget.vue b/frontend/src/components/DashboardWidget.vue index b3e6433..397b1e8 100644 --- a/frontend/src/components/DashboardWidget.vue +++ b/frontend/src/components/DashboardWidget.vue @@ -5,12 +5,12 @@ :data-widget-id="widgetId" >
- ⋮⋮ + ⋮⋮ {{ title }}
-
Laden…
+
{{ $t('widgets.dashboard.loading') }}
{{ error }}
diff --git a/frontend/src/components/MessageboxWidget.vue b/frontend/src/components/MessageboxWidget.vue index 1245410..4207bc6 100644 --- a/frontend/src/components/MessageboxWidget.vue +++ b/frontend/src/components/MessageboxWidget.vue @@ -9,7 +9,7 @@ {{ message }}
diff --git a/frontend/src/components/SettingsWidget.vue b/frontend/src/components/SettingsWidget.vue index b9fb7bc..4eed3c8 100644 --- a/frontend/src/components/SettingsWidget.vue +++ b/frontend/src/components/SettingsWidget.vue @@ -103,10 +103,6 @@ export default { type: String, required: true } - }, - data: { - settings: [], - possibleVisibilities: [], }, computed: { ...mapGetters(['user']), @@ -170,6 +166,14 @@ export default { settingId: settingId, value: value }); + if (setting?.name === 'language' && Array.isArray(setting.options)) { + const opt = setting.options.find((o) => String(o.id) === String(value)); + const code = opt?.value; + const supported = ['de', 'en', 'ceb', 'es']; + if (code && supported.includes(code)) { + this.$store.dispatch('setLanguage', code); + } + } this.fetchSettings(); } catch (err) { console.error('Error updating setting:', err); @@ -238,6 +242,7 @@ export default { data() { return { settings: [], + possibleVisibilities: [], userEmail: "", userUsername: "", }; diff --git a/frontend/src/components/TermineWidget.vue b/frontend/src/components/TermineWidget.vue index e218eff..7eea212 100644 --- a/frontend/src/components/TermineWidget.vue +++ b/frontend/src/components/TermineWidget.vue @@ -1,10 +1,10 @@ @@ -37,10 +37,19 @@ export default { } }, methods: { + getDateLocale() { + const locale = this.$i18n?.locale; + return { + de: 'de-DE', + en: 'en-GB', + es: 'es-ES', + ceb: 'fil-PH' + }[locale] || 'de-DE'; + }, formatDate(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr); - return d.toLocaleDateString('de-DE', { + return d.toLocaleDateString(this.getDateLocale(), { day: 'numeric', month: 'short' }); diff --git a/frontend/src/components/widgets/FalukantWidget.vue b/frontend/src/components/widgets/FalukantWidget.vue index 6025aaa..42413ba 100644 --- a/frontend/src/components/widgets/FalukantWidget.vue +++ b/frontend/src/components/widgets/FalukantWidget.vue @@ -21,7 +21,7 @@ - + {{ $t('widgets.falukant.emptyValue') }} +