feat(localization): expand language support and enhance UI for user settings
All checks were successful
Deploy to production / deploy (push) Successful in 3m0s
All checks were successful
Deploy to production / deploy (push) Successful in 3m0s
- 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:
@@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
import User from '../models/community/user.js';
|
import User from '../models/community/user.js';
|
||||||
import UserParam from '../models/community/user_param.js';
|
import UserParam from '../models/community/user_param.js';
|
||||||
import UserParamType from '../models/type/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 { sendAccountActivationEmail, sendPasswordResetEmail } from './emailService.js';
|
||||||
import { sequelize } from '../utils/sequelize.js';
|
import { sequelize } from '../utils/sequelize.js';
|
||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
@@ -143,6 +144,16 @@ export const loginUser = async ({ username, password }) => {
|
|||||||
const mappedParams = params.map(param => {
|
const mappedParams = params.map(param => {
|
||||||
return { 'name': param.paramType.description, 'value': param.value };
|
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');
|
console.log('return user');
|
||||||
return {
|
return {
|
||||||
id: user.hashedId,
|
id: user.hashedId,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ const initializeTypes = async () => {
|
|||||||
}
|
}
|
||||||
const valuesList = {
|
const valuesList = {
|
||||||
gender: ['male', 'female', 'transfemale', 'transmale', 'nonbinary'],
|
gender: ['male', 'female', 'transfemale', 'transmale', 'nonbinary'],
|
||||||
language: ['de', 'en'],
|
language: ['de', 'en', 'ceb', 'es'],
|
||||||
eyecolor: ['blue', 'green', 'brown', 'black', 'grey', 'hazel', 'amber', 'red', 'other'],
|
eyecolor: ['blue', 'green', 'brown', 'black', 'grey', 'hazel', 'amber', 'red', 'other'],
|
||||||
haircolor: ['black', 'brown', 'blonde', 'red', 'grey', 'white', 'other'],
|
haircolor: ['black', 'brown', 'blonde', 'red', 'grey', 'white', 'other'],
|
||||||
hairlength: ['short', 'medium', 'long', 'bald', 'other'],
|
hairlength: ['short', 'medium', 'long', 'bald', 'other'],
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import WidgetType from '../models/type/widget_type.js';
|
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 = [
|
const DEFAULT_WIDGET_TYPES = [
|
||||||
{ label: 'Termine', endpoint: '/api/termine', description: 'Bevorstehende Termine', orderId: 1 },
|
{ label: 'Termine', endpoint: '/api/termine', description: 'Bevorstehende Termine', orderId: 1 },
|
||||||
{ label: 'Falukant', endpoint: '/api/falukant/dashboard-widget', description: 'Charakter, Geld, Nachrichten, Kinder', orderId: 2 },
|
{ label: 'Falukant', endpoint: '/api/falukant/dashboard-widget', description: 'Charakter, Geld, Nachrichten, Kinder', orderId: 2 },
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
<div class="footer-system">
|
<div class="footer-system">
|
||||||
<button class="footer-brand" type="button" @click="showFalukantDaemonStatus">
|
<button class="footer-brand" type="button" @click="showFalukantDaemonStatus">
|
||||||
<img src="/images/icons/logo_color.png" alt="YourPart" />
|
<img src="/images/icons/logo_color.png" alt="YourPart" />
|
||||||
<span>System</span>
|
<span>{{ $t('appShell.footer.systemLabel') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<span class="footer-caption">
|
<span class="footer-caption">
|
||||||
{{ openDialogs.length === 0 ? 'Keine offenen Dialoge' : `${openDialogs.length} Fenster aktiv` }}
|
{{ openDialogs.length === 0
|
||||||
|
? $t('appShell.footer.noOpenDialogs')
|
||||||
|
: $t('appShell.footer.activeWindows', { count: openDialogs.length }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
<span class="button-text">{{ dialog.dialog.isTitleTranslated ? $t(dialog.dialog.localTitle) :
|
<span class="button-text">{{ dialog.dialog.isTitleTranslated ? $t(dialog.dialog.localTitle) :
|
||||||
dialog.dialog.localTitle }}</span>
|
dialog.dialog.localTitle }}</span>
|
||||||
</button>
|
</button>
|
||||||
<span v-if="openDialogs.length === 0" class="window-bar__empty">System bereit</span>
|
<span v-if="openDialogs.length === 0" class="window-bar__empty">{{ $t('appShell.footer.systemReady') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="static-block">
|
<div class="static-block">
|
||||||
@@ -72,7 +74,7 @@ export default {
|
|||||||
},
|
},
|
||||||
// Daemon WebSocket deaktiviert - diese Funktionen sind nicht mehr verfügbar
|
// Daemon WebSocket deaktiviert - diese Funktionen sind nicht mehr verfügbar
|
||||||
async showFalukantDaemonStatus() {
|
async showFalukantDaemonStatus() {
|
||||||
showInfo(this, 'Der Systemstatus ist in dieser Ansicht derzeit nicht direkt verfügbar.');
|
showInfo(this, this.$t('appShell.footer.systemStatusUnavailable'));
|
||||||
},
|
},
|
||||||
handleDaemonMessage() {
|
handleDaemonMessage() {
|
||||||
// Status-Events werden hier bewusst nicht verarbeitet.
|
// Status-Events werden hier bewusst nicht verarbeitet.
|
||||||
|
|||||||
@@ -5,21 +5,34 @@
|
|||||||
<div class="logo"><img src="/images/logos/logo.png" alt="YourPart" /></div>
|
<div class="logo"><img src="/images/logos/logo.png" alt="YourPart" /></div>
|
||||||
<div class="brand-copy">
|
<div class="brand-copy">
|
||||||
<strong>YourPart</strong>
|
<strong>YourPart</strong>
|
||||||
<span>Community-Plattform</span>
|
<span>{{ $t('appShell.header.tagline') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-meta">
|
<div class="header-meta">
|
||||||
<div class="header-meta__context">
|
<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>
|
||||||
<div class="connection-status" v-if="isLoggedIn">
|
<div class="connection-status" v-if="isLoggedIn">
|
||||||
<div class="status-indicator" :class="backendStatusClass">
|
<div class="status-indicator" :class="backendStatusClass">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="status-text">Backend</span>
|
<span class="status-text">{{ $t('appShell.header.backend') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-indicator" :class="daemonStatusClass">
|
<div class="status-indicator" :class="daemonStatusClass">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span class="status-text">Daemon</span>
|
<span class="status-text">{{ $t('appShell.header.daemon') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,11 +42,22 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import apiClient from '@/utils/axios.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AppHeader',
|
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: {
|
computed: {
|
||||||
...mapGetters(['isLoggedIn', 'connectionStatus', 'daemonConnectionStatus']),
|
...mapGetters(['isLoggedIn', 'user', 'language', 'connectionStatus', 'daemonConnectionStatus']),
|
||||||
backendStatusClass() {
|
backendStatusClass() {
|
||||||
return {
|
return {
|
||||||
'status-connected': this.connectionStatus === 'connected',
|
'status-connected': this.connectionStatus === 'connected',
|
||||||
@@ -50,7 +74,40 @@ export default {
|
|||||||
'status-error': this.daemonConnectionStatus === 'error'
|
'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>
|
</script>
|
||||||
|
|
||||||
@@ -144,6 +201,38 @@ export default {
|
|||||||
color: #8a5411;
|
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 {
|
.connection-status {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
:data-widget-id="widgetId"
|
:data-widget-id="widgetId"
|
||||||
>
|
>
|
||||||
<header class="dashboard-widget__titlebar">
|
<header class="dashboard-widget__titlebar">
|
||||||
<span class="dashboard-widget__drag-handle" title="Verschieben" draggable="true" @dragstart="onDragStart" @dragend="onDragEnd">⋮⋮</span>
|
<span class="dashboard-widget__drag-handle" :title="$t('widgets.dashboard.dragHandle')" draggable="true" @dragstart="onDragStart" @dragend="onDragEnd">⋮⋮</span>
|
||||||
<span class="dashboard-widget__title">{{ title }}</span>
|
<span class="dashboard-widget__title">{{ title }}</span>
|
||||||
<slot name="title-actions"></slot>
|
<slot name="title-actions"></slot>
|
||||||
</header>
|
</header>
|
||||||
<div class="dashboard-widget__frame">
|
<div class="dashboard-widget__frame">
|
||||||
<div v-if="loading" class="dashboard-widget__state">Laden…</div>
|
<div v-if="loading" class="dashboard-widget__state">{{ $t('widgets.dashboard.loading') }}</div>
|
||||||
<div v-else-if="error" class="dashboard-widget__state dashboard-widget__error">{{ error }}</div>
|
<div v-else-if="error" class="dashboard-widget__state dashboard-widget__error">{{ error }}</div>
|
||||||
<div v-else class="dashboard-widget__body">
|
<div v-else class="dashboard-widget__body">
|
||||||
<component :is="widgetComponent" :data="data" />
|
<component :is="widgetComponent" :data="data" />
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<button @click="close()" class="dialog-button">Ok</button>
|
<button @click="close()" class="dialog-button">{{ $t('general.ok') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -103,10 +103,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: {
|
|
||||||
settings: [],
|
|
||||||
possibleVisibilities: [],
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['user']),
|
...mapGetters(['user']),
|
||||||
@@ -170,6 +166,14 @@ export default {
|
|||||||
settingId: settingId,
|
settingId: settingId,
|
||||||
value: value
|
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();
|
this.fetchSettings();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error updating setting:', err);
|
console.error('Error updating setting:', err);
|
||||||
@@ -238,6 +242,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
settings: [],
|
settings: [],
|
||||||
|
possibleVisibilities: [],
|
||||||
userEmail: "",
|
userEmail: "",
|
||||||
userUsername: "",
|
userUsername: "",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="termine-widget">
|
<div class="termine-widget">
|
||||||
<h2>📅 Termine</h2>
|
<h2>{{ $t('widgets.appointments.title') }}</h2>
|
||||||
<div v-if="loading" class="loading">Lade Termine...</div>
|
<div v-if="loading" class="loading">{{ $t('widgets.appointments.loading') }}</div>
|
||||||
<div v-else-if="error" class="error">{{ error }}</div>
|
<div v-else-if="error" class="error">{{ error }}</div>
|
||||||
<div v-else-if="termine.length === 0" class="no-termine">
|
<div v-else-if="termine.length === 0" class="no-termine">
|
||||||
Keine bevorstehenden Termine
|
{{ $t('widgets.appointments.empty') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="termine-list">
|
<div v-else class="termine-list">
|
||||||
<div v-for="termin in termine" :key="termin.datum + termin.titel" class="termin-item">
|
<div v-for="termin in termine" :key="termin.datum + termin.titel" class="termin-item">
|
||||||
@@ -50,7 +50,7 @@ export default {
|
|||||||
this.termine = response.data;
|
this.termine = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading termine:', error);
|
console.error('Error loading termine:', error);
|
||||||
this.error = 'Termine konnten nicht geladen werden';
|
this.error = this.$t('widgets.appointments.loadError');
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,16 @@ export default {
|
|||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric'
|
day: 'numeric'
|
||||||
};
|
};
|
||||||
return date.toLocaleDateString('de-DE', options);
|
return date.toLocaleDateString(this.getDateLocale(), options);
|
||||||
|
},
|
||||||
|
getDateLocale() {
|
||||||
|
const locale = this.$i18n?.locale;
|
||||||
|
return {
|
||||||
|
de: 'de-DE',
|
||||||
|
en: 'en-GB',
|
||||||
|
es: 'es-ES',
|
||||||
|
ceb: 'fil-PH'
|
||||||
|
}[locale] || 'de-DE';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<th>{{ $t('falukant.branch.sale.quality') }}</th>
|
<th>{{ $t('falukant.branch.sale.quality') }}</th>
|
||||||
<th>{{ $t('falukant.branch.sale.quantity') }}</th>
|
<th>{{ $t('falukant.branch.sale.quantity') }}</th>
|
||||||
<th>{{ $t('falukant.branch.sale.sell') }}</th>
|
<th>{{ $t('falukant.branch.sale.sell') }}</th>
|
||||||
<th>Bessere Preise</th>
|
<th>{{ $t('falukant.branch.revenue.betterPrices') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -165,7 +165,7 @@
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
showError(this, 'Fehler beim Kaufen eines Teils der Lagerkapazität.');
|
showError(this, this.$t('falukant.branch.storage.buyPartialError'));
|
||||||
}
|
}
|
||||||
remainingAmount -= toBuy;
|
remainingAmount -= toBuy;
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
.then(() => this.loadStorageData())
|
.then(() => this.loadStorageData())
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
showError(this, 'Fehler beim Verkaufen der Lagerkapazität.');
|
showError(this, this.$t('falukant.branch.storage.sellError'));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getCostOfType(labelTr) {
|
getCostOfType(labelTr) {
|
||||||
|
|||||||
@@ -9,19 +9,19 @@
|
|||||||
<div class="birthday-info">
|
<div class="birthday-info">
|
||||||
<div class="birthday-name">{{ birthday.username }}</div>
|
<div class="birthday-name">{{ birthday.username }}</div>
|
||||||
<div class="birthday-date">
|
<div class="birthday-date">
|
||||||
<span v-if="birthday.daysUntil === 0" class="birthday-highlight">Heute!</span>
|
<span v-if="birthday.daysUntil === 0" class="birthday-highlight">{{ $t('widgets.birthdays.today') }}</span>
|
||||||
<span v-else-if="birthday.daysUntil === 1">Morgen</span>
|
<span v-else-if="birthday.daysUntil === 1">{{ $t('widgets.birthdays.tomorrow') }}</span>
|
||||||
<span v-else>{{ formatDate(birthday.nextDate) }}</span>
|
<span v-else>{{ formatDate(birthday.nextDate) }}</span>
|
||||||
<span class="birthday-age">(wird {{ birthday.turningAge }})</span>
|
<span class="birthday-age">{{ $t('widgets.birthdays.turningAge', { age: birthday.turningAge }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="birthday.daysUntil > 1" class="birthday-days">
|
<div v-if="birthday.daysUntil > 1" class="birthday-days">
|
||||||
{{ birthday.daysUntil }} Tage
|
{{ $t('widgets.birthdays.inDays', { count: birthday.daysUntil }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="birthday-empty">
|
<div v-else class="birthday-empty">
|
||||||
Keine Geburtstage von Freunden sichtbar
|
{{ $t('widgets.birthdays.empty') }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -37,10 +37,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
formatDate(dateStr) {
|
||||||
if (!dateStr) return '';
|
if (!dateStr) return '';
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
return d.toLocaleDateString('de-DE', {
|
return d.toLocaleDateString(this.getDateLocale(), {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short'
|
month: 'short'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</dd>
|
</dd>
|
||||||
</template>
|
</template>
|
||||||
</dl>
|
</dl>
|
||||||
<span v-else>—</span>
|
<span v-else>{{ $t('widgets.falukant.emptyValue') }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -62,7 +62,7 @@ export default {
|
|||||||
},
|
},
|
||||||
falukantDisplayName() {
|
falukantDisplayName() {
|
||||||
const d = this.falukantData;
|
const d = this.falukantData;
|
||||||
if (!d) return '—';
|
if (!d) return this.$t('widgets.falukant.emptyValue');
|
||||||
const titleKey = d.titleLabelTr;
|
const titleKey = d.titleLabelTr;
|
||||||
const gender = d.gender;
|
const gender = d.gender;
|
||||||
const nameWithoutTitle = d.nameWithoutTitle ?? d.characterName;
|
const nameWithoutTitle = d.nameWithoutTitle ?? d.characterName;
|
||||||
@@ -71,11 +71,11 @@ export default {
|
|||||||
const translatedTitle = this.$t(key);
|
const translatedTitle = this.$t(key);
|
||||||
if (translatedTitle !== key) return `${translatedTitle} ${nameWithoutTitle}`.trim();
|
if (translatedTitle !== key) return `${translatedTitle} ${nameWithoutTitle}`.trim();
|
||||||
}
|
}
|
||||||
return d.characterName || nameWithoutTitle || '—';
|
return d.characterName || nameWithoutTitle || this.$t('widgets.falukant.emptyValue');
|
||||||
},
|
},
|
||||||
falukantGenderLabel() {
|
falukantGenderLabel() {
|
||||||
const g = this.falukantData?.gender;
|
const g = this.falukantData?.gender;
|
||||||
if (g == null || g === '') return '—';
|
if (g == null || g === '') return this.$t('widgets.falukant.emptyValue');
|
||||||
|
|
||||||
// Altersabhängige, (auf Wunsch) altertümlichere Bezeichnungen
|
// Altersabhängige, (auf Wunsch) altertümlichere Bezeichnungen
|
||||||
const years = this._ageYearsFromWidgetValue(this.falukantData?.age);
|
const years = this._ageYearsFromWidgetValue(this.falukantData?.age);
|
||||||
@@ -93,9 +93,9 @@ export default {
|
|||||||
},
|
},
|
||||||
falukantAgeLabel() {
|
falukantAgeLabel() {
|
||||||
const ageValue = this.falukantData?.age;
|
const ageValue = this.falukantData?.age;
|
||||||
if (ageValue == null) return '—';
|
if (ageValue == null) return this.$t('widgets.falukant.emptyValue');
|
||||||
const years = this._ageYearsFromWidgetValue(ageValue);
|
const years = this._ageYearsFromWidgetValue(ageValue);
|
||||||
if (years == null) return '—';
|
if (years == null) return this.$t('widgets.falukant.emptyValue');
|
||||||
return `${years} ${this.$t('falukant.overview.metadata.years')}`;
|
return `${years} ${this.$t('falukant.overview.metadata.years')}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -145,8 +145,17 @@ export default {
|
|||||||
},
|
},
|
||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
const n = Number(value);
|
const n = Number(value);
|
||||||
if (Number.isNaN(n)) return '—';
|
if (Number.isNaN(n)) return this.$t('widgets.falukant.emptyValue');
|
||||||
return n.toLocaleString('de-DE');
|
return n.toLocaleString(this.getNumberLocale());
|
||||||
|
},
|
||||||
|
getNumberLocale() {
|
||||||
|
const locale = this.$i18n?.locale;
|
||||||
|
return {
|
||||||
|
de: 'de-DE',
|
||||||
|
en: 'en-GB',
|
||||||
|
es: 'es-ES',
|
||||||
|
ceb: 'fil-PH'
|
||||||
|
}[locale] || 'de-DE';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,21 +30,32 @@ export default {
|
|||||||
fallbackText() {
|
fallbackText() {
|
||||||
if (this.data == null) return '';
|
if (this.data == null) return '';
|
||||||
if (Array.isArray(this.data)) {
|
if (Array.isArray(this.data)) {
|
||||||
return this.data.length === 0 ? 'Keine Einträge' : `(${this.data.length} Einträge)`;
|
return this.data.length === 0
|
||||||
|
? this.$t('widgets.list.noEntries')
|
||||||
|
: this.$t('widgets.list.entriesCount', { count: this.data.length });
|
||||||
}
|
}
|
||||||
if (typeof this.data === 'object') {
|
if (typeof this.data === 'object') {
|
||||||
const keys = Object.keys(this.data);
|
const keys = Object.keys(this.data);
|
||||||
return keys.length === 0 ? '—' : `(${keys.length} Felder)`;
|
return keys.length === 0 ? '—' : this.$t('widgets.list.fieldsCount', { count: keys.length });
|
||||||
}
|
}
|
||||||
return String(this.data);
|
return String(this.data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getDateLocale() {
|
||||||
|
const locale = this.$i18n?.locale;
|
||||||
|
return {
|
||||||
|
de: 'de-DE',
|
||||||
|
en: 'en-GB',
|
||||||
|
es: 'es-ES',
|
||||||
|
ceb: 'fil-PH'
|
||||||
|
}[locale] || 'de-DE';
|
||||||
|
},
|
||||||
formatDatum(dateStr) {
|
formatDatum(dateStr) {
|
||||||
if (!dateStr) return '';
|
if (!dateStr) return '';
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
if (Number.isNaN(d.getTime())) return String(dateStr);
|
if (Number.isNaN(d.getTime())) return String(dateStr);
|
||||||
return d.toLocaleDateString('de-DE', {
|
return d.toLocaleDateString(this.getDateLocale(), {
|
||||||
weekday: 'short',
|
weekday: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="dashboard-widget__news-title"
|
class="dashboard-widget__news-title"
|
||||||
>
|
>
|
||||||
{{ article.title || '—' }}
|
{{ article.title || $t('widgets.news.emptyValue') }}
|
||||||
</a>
|
</a>
|
||||||
<span v-else class="dashboard-widget__title-text">{{ article.title || '—' }}</span>
|
<span v-else class="dashboard-widget__title-text">{{ article.title || $t('widgets.news.emptyValue') }}</span>
|
||||||
<span v-if="article.pubDate" class="dashboard-widget__date">{{ formatNewsDate(article.pubDate) }}</span>
|
<span v-if="article.pubDate" class="dashboard-widget__date">{{ formatNewsDate(article.pubDate) }}</span>
|
||||||
<p v-if="article.description" class="dashboard-widget__desc">{{ article.description }}</p>
|
<p v-if="article.description" class="dashboard-widget__desc">{{ article.description }}</p>
|
||||||
</article>
|
</article>
|
||||||
<span v-else>—</span>
|
<span v-else>{{ $t('widgets.news.emptyValue') }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -32,11 +32,20 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getDateLocale() {
|
||||||
|
const locale = this.$i18n?.locale;
|
||||||
|
return {
|
||||||
|
de: 'de-DE',
|
||||||
|
en: 'en-GB',
|
||||||
|
es: 'es-ES',
|
||||||
|
ceb: 'fil-PH'
|
||||||
|
}[locale] || 'de-DE';
|
||||||
|
},
|
||||||
formatNewsDate(dateStr) {
|
formatNewsDate(dateStr) {
|
||||||
if (!dateStr) return '';
|
if (!dateStr) return '';
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
if (Number.isNaN(d.getTime())) return String(dateStr);
|
if (Number.isNaN(d.getTime())) return String(dateStr);
|
||||||
return d.toLocaleDateString('de-DE', {
|
return d.toLocaleDateString(this.getDateLocale(), {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
|
|||||||
@@ -14,16 +14,16 @@
|
|||||||
<div class="upcoming-date">
|
<div class="upcoming-date">
|
||||||
{{ formatDate(event.datum) }}
|
{{ formatDate(event.datum) }}
|
||||||
<span v-if="event.startTime && !event.allDay" class="upcoming-time">
|
<span v-if="event.startTime && !event.allDay" class="upcoming-time">
|
||||||
{{ event.startTime }} Uhr
|
{{ $t('widgets.upcoming.timeAt', { time: event.startTime }) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="event.allDay" class="upcoming-allday">Ganztägig</span>
|
<span v-if="event.allDay" class="upcoming-allday">{{ $t('widgets.upcoming.allDay') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="event.beschreibung" class="upcoming-desc">{{ event.beschreibung }}</div>
|
<div v-if="event.beschreibung" class="upcoming-desc">{{ event.beschreibung }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="upcoming-empty">
|
<div v-else class="upcoming-empty">
|
||||||
Keine anstehenden Termine
|
{{ $t('widgets.upcoming.empty') }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -50,6 +50,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
formatDate(dateStr) {
|
||||||
if (!dateStr) return '';
|
if (!dateStr) return '';
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
@@ -58,13 +67,13 @@ export default {
|
|||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||||
|
|
||||||
if (d.toDateString() === today.toDateString()) {
|
if (d.toDateString() === today.toDateString()) {
|
||||||
return 'Heute';
|
return this.$t('widgets.upcoming.today');
|
||||||
}
|
}
|
||||||
if (d.toDateString() === tomorrow.toDateString()) {
|
if (d.toDateString() === tomorrow.toDateString()) {
|
||||||
return 'Morgen';
|
return this.$t('widgets.upcoming.tomorrow');
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.toLocaleDateString('de-DE', {
|
return d.toLocaleDateString(this.getDateLocale(), {
|
||||||
weekday: 'short',
|
weekday: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short'
|
month: 'short'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<DialogWidget ref="dialog" :title="$t('socialnetwork.profile.pretitle')" :isTitleTranslated="isTitleTranslated"
|
<DialogWidget ref="dialog" :title="$t('socialnetwork.profile.pretitle')" :isTitleTranslated="isTitleTranslated"
|
||||||
:show-close="true" :buttons="[{ text: 'Ok', action: 'close' }]" :modal="false" @close="closeDialog" height="75%"
|
:show-close="true" :buttons="profileDialogButtons" :modal="false" @close="closeDialog" height="75%"
|
||||||
name="UserProfileDialog" display="flex">
|
name="UserProfileDialog" display="flex">
|
||||||
<div class="activities">
|
<div class="activities">
|
||||||
<span>{{ $t(`socialnetwork.friendship.state.${friendshipState}`) }}</span>
|
<span>{{ $t(`socialnetwork.friendship.state.${friendshipState}`) }}</span>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="profile-content">
|
<div class="profile-content">
|
||||||
<div>
|
<div>
|
||||||
<ul class="tab-list">
|
<ul class="tab-list">
|
||||||
<li v-for="tab in tabs" :key="tab.name" :class="{ active: activeTab === tab.name }"
|
<li v-for="tab in profileTabs" :key="tab.name" :class="{ active: activeTab === tab.name }"
|
||||||
@click="selectTab(tab.name)">
|
@click="selectTab(tab.name)">
|
||||||
{{ tab.label }}
|
{{ tab.label }}
|
||||||
</li>
|
</li>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<ul v-if="images.length > 0" class="image-list">
|
<ul v-if="images.length > 0" class="image-list">
|
||||||
<li v-for="image in images" :key="image.id" @click="openImageDialog(image)">
|
<li v-for="image in images" :key="image.id" @click="openImageDialog(image)">
|
||||||
<img :src="image.url || image.placeholder" alt="Loading..." />
|
<img :src="image.url || image.placeholder" :alt="$t('socialnetwork.gallery.imageLoadingAlt')" />
|
||||||
<p>{{ image.title }}</p>
|
<p>{{ image.title }}</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
}}</label>
|
}}</label>
|
||||||
<input type="file" @change="onFileChange" accept="image/*" />
|
<input type="file" @change="onFileChange" accept="image/*" />
|
||||||
<div v-if="imagePreview" class="image-preview">
|
<div v-if="imagePreview" class="image-preview">
|
||||||
<img :src="imagePreview" alt="Image Preview"
|
<img :src="imagePreview" :alt="$t('socialnetwork.gallery.imagePreviewAlt')"
|
||||||
style="max-width: 100px; max-height: 100px;" />
|
style="max-width: 100px; max-height: 100px;" />
|
||||||
</div>
|
</div>
|
||||||
<EditorContent :editor="editor" class="editor" />
|
<EditorContent :editor="editor" class="editor" />
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="guestbook-entries">
|
<div v-else class="guestbook-entries">
|
||||||
<div v-for="entry in guestbookEntries" :key="entry.id" class="guestbook-entry">
|
<div v-for="entry in guestbookEntries" :key="entry.id" class="guestbook-entry">
|
||||||
<img v-if="entry.image" :src="entry.image.url" alt="Entry Image"
|
<img v-if="entry.image" :src="entry.image.url" :alt="$t('socialnetwork.profile.guestbook.entryImageAlt')"
|
||||||
style="max-width: 400px; max-height: 400px;" />
|
style="max-width: 400px; max-height: 400px;" />
|
||||||
<p v-html="sanitizedContent(entry)"></p>
|
<p v-html="sanitizedContent(entry)"></p>
|
||||||
<div class="entry-info">
|
<div class="entry-info">
|
||||||
@@ -117,13 +117,23 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['user']),
|
...mapGetters(['user']),
|
||||||
|
profileDialogButtons() {
|
||||||
|
return [{ text: this.$t('general.ok'), action: 'close' }];
|
||||||
|
},
|
||||||
canOpenEroticPictures() {
|
canOpenEroticPictures() {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
this.userProfile?.username &&
|
this.userProfile?.username &&
|
||||||
this.user?.username &&
|
this.user?.username &&
|
||||||
this.userProfile.username !== this.user.username
|
this.userProfile.username !== this.user.username
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
profileTabs() {
|
||||||
|
return [
|
||||||
|
{ name: 'general', label: this.$t('socialnetwork.profile.tab.general') },
|
||||||
|
{ name: 'images', label: this.$t('socialnetwork.profile.tab.images') },
|
||||||
|
{ name: 'guestbook', label: this.$t('socialnetwork.profile.tab.guestbook') },
|
||||||
|
];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -141,11 +151,6 @@ export default {
|
|||||||
selectedImage: null,
|
selectedImage: null,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
tabs: [
|
|
||||||
{ name: 'general', label: this.$t('socialnetwork.profile.tab.general') },
|
|
||||||
{ name: 'images', label: this.$t('socialnetwork.profile.tab.images') },
|
|
||||||
{ name: 'guestbook', label: this.$t('socialnetwork.profile.tab.guestbook') }
|
|
||||||
],
|
|
||||||
apiKey: import.meta.env.VITE_TINYMCE_API_KEY,
|
apiKey: import.meta.env.VITE_TINYMCE_API_KEY,
|
||||||
editor: null,
|
editor: null,
|
||||||
hasSendFriendshipRequest: false,
|
hasSendFriendshipRequest: false,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import cebActivate from './locales/ceb/activate.json';
|
|||||||
import cebError from './locales/ceb/error.json';
|
import cebError from './locales/ceb/error.json';
|
||||||
import cebMessage from './locales/ceb/message.json';
|
import cebMessage from './locales/ceb/message.json';
|
||||||
import cebSettings from './locales/ceb/settings.json';
|
import cebSettings from './locales/ceb/settings.json';
|
||||||
|
import cebAdmin from './locales/ceb/admin.json';
|
||||||
import cebPasswordReset from './locales/ceb/passwordReset.json';
|
import cebPasswordReset from './locales/ceb/passwordReset.json';
|
||||||
import cebSocialNetwork from './locales/ceb/socialnetwork.json';
|
import cebSocialNetwork from './locales/ceb/socialnetwork.json';
|
||||||
import cebFriends from './locales/ceb/friends.json';
|
import cebFriends from './locales/ceb/friends.json';
|
||||||
@@ -124,6 +125,7 @@ const messages = {
|
|||||||
...cebError,
|
...cebError,
|
||||||
...cebMessage,
|
...cebMessage,
|
||||||
...cebSettings,
|
...cebSettings,
|
||||||
|
...cebAdmin,
|
||||||
...cebPasswordReset,
|
...cebPasswordReset,
|
||||||
...cebSocialNetwork,
|
...cebSocialNetwork,
|
||||||
...cebFriends,
|
...cebFriends,
|
||||||
@@ -178,7 +180,10 @@ const messages = {
|
|||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: store.state.language,
|
locale: store.state.language,
|
||||||
fallbackLocale: 'de',
|
fallbackLocale: {
|
||||||
|
ceb: ['en', 'de'],
|
||||||
|
default: ['de']
|
||||||
|
},
|
||||||
messages
|
messages
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
41
frontend/src/i18n/locales/ceb/admin.json
Normal file
41
frontend/src/i18n/locales/ceb/admin.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"admin": {
|
||||||
|
"interests": {
|
||||||
|
"title": "[Admin] - Pagdumala sa mga interes"
|
||||||
|
},
|
||||||
|
"contacts": {
|
||||||
|
"title": "[Admin] - Mga hangyo sa pakigkontak",
|
||||||
|
"date": "Petsa",
|
||||||
|
"from": "Gikan kang",
|
||||||
|
"actions": "Mga aksyon",
|
||||||
|
"open": "Ablihi",
|
||||||
|
"finished": "Tapusa"
|
||||||
|
},
|
||||||
|
"editcontactrequest": {
|
||||||
|
"title": "[Admin] - Usba ang hangyo sa pakigkontak"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "Ngalan sa user",
|
||||||
|
"active": "Aktibo",
|
||||||
|
"blocked": "Gi-block",
|
||||||
|
"actions": "Mga aksyon",
|
||||||
|
"search": "Pangita"
|
||||||
|
},
|
||||||
|
"rights": {
|
||||||
|
"add": "Idugang ang katungod",
|
||||||
|
"select": "Palihog pagpili",
|
||||||
|
"current": "Karon nga mga katungod"
|
||||||
|
},
|
||||||
|
"forum": {
|
||||||
|
"title": "[Admin] - Forum",
|
||||||
|
"currentForums": "Anaa nang mga forum",
|
||||||
|
"edit": "Usba",
|
||||||
|
"delete": "Tangtanga",
|
||||||
|
"createForum": "Paghimo",
|
||||||
|
"forumName": "Titulo",
|
||||||
|
"create": "Paghimo",
|
||||||
|
"selectPermissions": "Palihog pagpili",
|
||||||
|
"confirmDeleteTitle": "Tangtanga ang forum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -306,6 +306,10 @@
|
|||||||
"sale": {
|
"sale": {
|
||||||
"runningGuards": "Mga guwardiya"
|
"runningGuards": "Mga guwardiya"
|
||||||
},
|
},
|
||||||
|
"storage": {
|
||||||
|
"buyPartialError": "Sayop sa pagpalit sa usa ka bahin sa kapasidad sa storage.",
|
||||||
|
"sellError": "Sayop sa pagbaligya sa kapasidad sa storage."
|
||||||
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"title": "Produksyon",
|
"title": "Produksyon",
|
||||||
"info": "Mga detalye bahin sa produksyon sa branch.",
|
"info": "Mga detalye bahin sa produksyon sa branch.",
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"friends": {
|
"friends": {
|
||||||
|
"kicker": "Komunidad",
|
||||||
|
"intro": "Mga panaghigala, bukas nga mga hangyo ug nagpadayon nga mga kontak sa usa ka lugar.",
|
||||||
"title": "Mga higala",
|
"title": "Mga higala",
|
||||||
|
"stats": {
|
||||||
|
"existing": "Naa na",
|
||||||
|
"open": "Abli"
|
||||||
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"existing": "Naa na",
|
"existing": "Naa na",
|
||||||
"rejected": "Gibalibaran",
|
"rejected": "Gibalibaran",
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"welcome": "Maayong pag-abot sa YourPart",
|
"welcome": "Maayong pag-abot sa YourPart",
|
||||||
"imprint": {
|
"imprint": {
|
||||||
"title": "Imprint",
|
"title": "Legal nga pahibalo",
|
||||||
"button": "Imprint"
|
"button": "Legal nga pahibalo"
|
||||||
},
|
},
|
||||||
"dataPrivacy": {
|
"dataPrivacy": {
|
||||||
"title": "Patakaran sa pagpanalipod sa datos",
|
"title": "Patakaran sa pagpanalipod sa datos",
|
||||||
"button": "Pagpanalipod sa datos"
|
"button": "Pagpanalipod sa datos"
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"title": "Kontak",
|
"title": "Pakigkontak",
|
||||||
"button": "Kontak"
|
"button": "Pakigkontak"
|
||||||
},
|
},
|
||||||
"error-title": "Sayop",
|
"error-title": "Sayop",
|
||||||
"warning-title": "Pasidaan",
|
"warning-title": "Pasidaan",
|
||||||
@@ -30,6 +30,59 @@
|
|||||||
"message": {
|
"message": {
|
||||||
"close": "Isira"
|
"close": "Isira"
|
||||||
},
|
},
|
||||||
|
"appShell": {
|
||||||
|
"header": {
|
||||||
|
"tagline": "Plataporma sa komunidad",
|
||||||
|
"beta": "Beta",
|
||||||
|
"backend": "Backend",
|
||||||
|
"daemon": "Daemon",
|
||||||
|
"language": "Pinulongan"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"systemLabel": "Sistema",
|
||||||
|
"noOpenDialogs": "Walay abli nga mga dialog",
|
||||||
|
"activeWindows": "{count} ka bintana ang aktibo",
|
||||||
|
"systemReady": "Andam ang sistema",
|
||||||
|
"systemStatusUnavailable": "Ang status sa sistema dili diretso makita niining view karon."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"widgets": {
|
||||||
|
"dashboard": {
|
||||||
|
"dragHandle": "Balhinon",
|
||||||
|
"loading": "Nag-load..."
|
||||||
|
},
|
||||||
|
"birthdays": {
|
||||||
|
"today": "Karon!",
|
||||||
|
"tomorrow": "Ugma",
|
||||||
|
"turningAge": "(mag-{age})",
|
||||||
|
"inDays": "{count} ka adlaw",
|
||||||
|
"empty": "Walay makita nga kaadlawon sa mga higala"
|
||||||
|
},
|
||||||
|
"upcoming": {
|
||||||
|
"today": "Karon",
|
||||||
|
"tomorrow": "Ugma",
|
||||||
|
"timeAt": "{time}",
|
||||||
|
"allDay": "Tibuok adlaw",
|
||||||
|
"empty": "Walay umaabot nga mga appointment"
|
||||||
|
},
|
||||||
|
"appointments": {
|
||||||
|
"title": "📅 Mga appointment",
|
||||||
|
"loading": "Nag-load sa mga appointment...",
|
||||||
|
"empty": "Walay umaabot nga mga appointment",
|
||||||
|
"loadError": "Dili ma-load ang mga appointment"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"noEntries": "Walay mga entry",
|
||||||
|
"entriesCount": "({count} ka entry)",
|
||||||
|
"fieldsCount": "({count} ka field)"
|
||||||
|
},
|
||||||
|
"news": {
|
||||||
|
"emptyValue": "—"
|
||||||
|
},
|
||||||
|
"falukant": {
|
||||||
|
"emptyValue": "—"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"male": "Lalaki",
|
"male": "Lalaki",
|
||||||
"female": "Babaye",
|
"female": "Babaye",
|
||||||
@@ -63,7 +116,7 @@
|
|||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
"friends": "Mga higala",
|
"friends": "Mga higala",
|
||||||
"guestbook": "Guestbook",
|
"guestbook": "Libro sa bisita",
|
||||||
"search": "Pagpangita",
|
"search": "Pagpangita",
|
||||||
"gallery": "Galeriya",
|
"gallery": "Galeriya",
|
||||||
"forum": "Forum",
|
"forum": "Forum",
|
||||||
@@ -97,7 +150,7 @@
|
|||||||
"sexualitySettings": "Sekswalidad",
|
"sexualitySettings": "Sekswalidad",
|
||||||
"flirtSettings": "Flirt",
|
"flirtSettings": "Flirt",
|
||||||
"accountSettings": "Account",
|
"accountSettings": "Account",
|
||||||
"languageAssistantSettings": "Language assistant",
|
"languageAssistantSettings": "Katabang sa pinulongan",
|
||||||
"interests": "Mga interes",
|
"interests": "Mga interes",
|
||||||
"adminInterests": "Pagdumala sa interes",
|
"adminInterests": "Pagdumala sa interes",
|
||||||
"adminUsers": "Mga user",
|
"adminUsers": "Mga user",
|
||||||
|
|||||||
@@ -1,5 +1,54 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
|
"dashboard": {
|
||||||
|
"kicker": "Imong lugar",
|
||||||
|
"title": "Maayong pagbalik!",
|
||||||
|
"subtitle": "Imong personal nga agi-anan padulong sa komunidad, mga appointment, Falukant, ug mga nagpadayon nga kalihokan.",
|
||||||
|
"edit": "Usba ang dashboard",
|
||||||
|
"addWidget": "+ Idugang ang widget ...",
|
||||||
|
"addAgain": "Idugang pag-usab",
|
||||||
|
"done": "Human",
|
||||||
|
"sectionTitle": "Imong kinatibuk-ang tan-aw",
|
||||||
|
"sectionIntro": "Mahimong ibalhin ang mga widget ug usbon sa edit mode.",
|
||||||
|
"widgetTitlePlaceholder": "Titulo",
|
||||||
|
"removeWidget": "Tangtanga ang widget",
|
||||||
|
"remove": "Tangtanga",
|
||||||
|
"empty": "Wala pay mga widget. I-klik ang “Usba ang dashboard” ug dayon ang “+ Idugang ang widget”.",
|
||||||
|
"defaultAppointmentsWidget": "Mga appointment",
|
||||||
|
"loadError": "Dili ma-load ang dashboard.",
|
||||||
|
"saveError": "Dili masave ang dashboard.",
|
||||||
|
"widgetLabels": {
|
||||||
|
"appointments": "Mga appointment",
|
||||||
|
"falukant": "Falukant",
|
||||||
|
"news": "Balita",
|
||||||
|
"birthdays": "Mga adlawng natawhan",
|
||||||
|
"upcoming": "Umaabot nga mga appointment",
|
||||||
|
"calendar": "Kalendaryo"
|
||||||
|
},
|
||||||
|
"overview": {
|
||||||
|
"activeWidgetsLabel": "Aktibong mga widget",
|
||||||
|
"activeWidgetsText": "Modular ang imong dashboard ug mahimo kining usbon ang han-ay bisan kanus-a.",
|
||||||
|
"availableModulesLabel": "Magamit nga mga module",
|
||||||
|
"availableModulesText": "Mahimo nimong isagol ang community, kalendaryo, balita, ug Falukant nga mga module.",
|
||||||
|
"editModeLabel": "Edit mode",
|
||||||
|
"editModeActive": "Aktibo",
|
||||||
|
"editModeInactive": "Patay",
|
||||||
|
"editModeActiveText": "Mahimong dugangan ug usbon karon ang mga widget.",
|
||||||
|
"editModeInactiveText": "Nagpabiling pokus ug sayon basahon ang sulod."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vocabLanding": {
|
||||||
|
"eyebrow": "Pagkat-on og pinulongan online",
|
||||||
|
"title": "Ang trainer sa bokabularyo sa YourPart naghiusa sa pagkat-on, mga kurso ug praktis sa usa ka plataporma.",
|
||||||
|
"lead": "Magtrabaho og interaktibong mga leksiyon, palapdan ang imong bokabularyo ug gamita ang organisado nga sulod para sa makadasig nga agos sa pagkat-on diretso sa browser.",
|
||||||
|
"cta": "Sugdi nga libre",
|
||||||
|
"feature1Title": "Interaktibong mga kurso",
|
||||||
|
"feature1Text": "Ang mga kurso, leksiyon ug ehersisyo makatabang sa sistematikong pagtukod og bag-ong kahanas sa pinulongan.",
|
||||||
|
"feature2Title": "Nagtutok sa praktis",
|
||||||
|
"feature2Text": "Ang bokabularyo, gramatika ug balik-balik gituyo para sa adlaw-adlaw nga rutina sa pagkat-on.",
|
||||||
|
"feature3Title": "Parte sa komunidad",
|
||||||
|
"feature3Text": "Ang pinulongan nga lugar nahimutang sa mas dako nga plataporma sa komunidad nga adunay blog, forum ug chat."
|
||||||
|
},
|
||||||
"betaNoticeLabel": "Pahibalo sa beta:",
|
"betaNoticeLabel": "Pahibalo sa beta:",
|
||||||
"betaNoticeText": "Ang YourPart padayon pang gihimo. Adunay mga feature nga wala pa mahuman, adunay impormasyon nga kulang pa, ug posible pa nga adunay mga kausaban.",
|
"betaNoticeText": "Ang YourPart padayon pang gihimo. Adunay mga feature nga wala pa mahuman, adunay impormasyon nga kulang pa, ug posible pa nga adunay mga kausaban.",
|
||||||
"nologin": {
|
"nologin": {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"eroticChat": "Erotik chat"
|
"eroticChat": "Erotik chat"
|
||||||
},
|
},
|
||||||
"m-socialnetwork": {
|
"m-socialnetwork": {
|
||||||
"guestbook": "Guestbook",
|
"guestbook": "Libro sa bisita",
|
||||||
"blog": "Blog",
|
"blog": "Blog",
|
||||||
"usersearch": "Pagpangita og user",
|
"usersearch": "Pagpangita og user",
|
||||||
"forum": "Forum",
|
"forum": "Forum",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"interests": "Mga interes",
|
"interests": "Mga interes",
|
||||||
"notifications": "Mga pahibalo",
|
"notifications": "Mga pahibalo",
|
||||||
"sexuality": "Sekswalidad",
|
"sexuality": "Sekswalidad",
|
||||||
"languageAssistant": "Language assistant"
|
"languageAssistant": "Katabang sa pinulongan"
|
||||||
},
|
},
|
||||||
"m-administration": {
|
"m-administration": {
|
||||||
"contactrequests": "Mga hangyo sa kontak",
|
"contactrequests": "Mga hangyo sa kontak",
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"personal": {
|
"personal": {
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"title": "Kalendaryo",
|
"title": "Kalendaryo",
|
||||||
|
"kicker": "Pagplano",
|
||||||
|
"intro": "Mga appointment, mga adlawng natawhan ug imong kaugalingong mga entry sa usa ka organisado nga tan-aw.",
|
||||||
"today": "Karon",
|
"today": "Karon",
|
||||||
"newEntry": "Bag-ong entry",
|
"newEntry": "Bag-ong entry",
|
||||||
"editEntry": "Usba ang entry",
|
"editEntry": "Usba ang entry",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"register": {
|
"register": {
|
||||||
"title": "Pagrehistro sa yourPart",
|
"title": "Pagrehistro sa yourPart",
|
||||||
"email": "E-mail address",
|
"email": "E-mail address",
|
||||||
"username": "Username",
|
"username": "Ngalan sa user",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"repeatPassword": "Usba ang password",
|
"repeatPassword": "Usba ang password",
|
||||||
"language": "Pinulongan",
|
"language": "Pinulongan",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"usernameinuse": "Dili magamit ang username.",
|
"usernameinuse": "Dili magamit ang username.",
|
||||||
"validation": {
|
"validation": {
|
||||||
"invalidEmail": "Palihog isulod ang sakto nga email address.",
|
"invalidEmail": "Palihog isulod ang sakto nga email address.",
|
||||||
"usernameTooShort": "Ang username kinahanglan adunay labing menos 3 ka karakter.",
|
"usernameTooShort": "Ang ngalan sa user kinahanglan adunay labing menos 3 ka karakter.",
|
||||||
"passwordHint": "Kinahanglan labing menos 8 ka karakter.",
|
"passwordHint": "Kinahanglan labing menos 8 ka karakter.",
|
||||||
"passwordTooShort": "Mubo ra ang password."
|
"passwordTooShort": "Mubo ra ang password."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"de": "Aleman",
|
"de": "Aleman",
|
||||||
"en": "Iningles",
|
"en": "Iningles",
|
||||||
"ceb": "Bisaya"
|
"ceb": "Bisaya",
|
||||||
|
"es": "Espanyol"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
@@ -22,14 +23,14 @@
|
|||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"title": "Account",
|
"title": "Account",
|
||||||
"heroEyebrow": "Settings",
|
"heroEyebrow": "Mga setting",
|
||||||
"heroIntro": "Atimana ang username, email, password ug visibility sa usa ka lugar.",
|
"heroIntro": "Atimana ang ngalan sa user, email, password ug visibility sa usa ka lugar.",
|
||||||
"language": "Pinulongan",
|
"language": "Pinulongan",
|
||||||
"username": "Username",
|
"username": "Ngalan sa user",
|
||||||
"email": "E-mail address",
|
"email": "E-mail address",
|
||||||
"newpassword": "Password",
|
"newpassword": "Password",
|
||||||
"newpasswordretype": "Usba ang password",
|
"newpasswordretype": "Usba ang password",
|
||||||
"showinsearch": "Ipakita sa user search",
|
"showinsearch": "Ipakita sa pagpangita sa user",
|
||||||
"changeaction": "Usba ang datos sa user",
|
"changeaction": "Usba ang datos sa user",
|
||||||
"oldpassword": "Karaan nga password (gikinahanglan)",
|
"oldpassword": "Karaan nga password (gikinahanglan)",
|
||||||
"validation": {
|
"validation": {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"socialnetwork": {
|
"socialnetwork": {
|
||||||
"usersearch": {
|
"usersearch": {
|
||||||
|
"kicker": "Pagpangita sa komunidad",
|
||||||
|
"intro": "Pangitaa ang angay nga mga kontak sa komunidad pinaagi sa ngalan, edad ug sekso.",
|
||||||
|
"ageSeparator": "hangtod",
|
||||||
|
"resultsCount": "{count} ka resulta",
|
||||||
|
"openProfile": "Ablihi ang profile",
|
||||||
"title": "Pagpangita og user",
|
"title": "Pagpangita og user",
|
||||||
"username": "Username",
|
"username": "Ngalan sa user",
|
||||||
"age_from": "Edad gikan sa",
|
"age_from": "Edad gikan sa",
|
||||||
"age_to": "hangtod",
|
"age_to": "hangtod",
|
||||||
"gender": "Gender",
|
"gender": "Sekso",
|
||||||
"search_button": "Pangita",
|
"search_button": "Pangita",
|
||||||
"no_results": "Walay nakit-an nga resulta",
|
"no_results": "Walay nakit-an nga resulta",
|
||||||
"results_title": "Mga resulta sa pagpangita:",
|
"results_title": "Mga resulta sa pagpangita:",
|
||||||
@@ -22,8 +27,8 @@
|
|||||||
"tab": {
|
"tab": {
|
||||||
"general": "Kinatibuk-an",
|
"general": "Kinatibuk-an",
|
||||||
"sexuality": "Sekswalidad",
|
"sexuality": "Sekswalidad",
|
||||||
"images": "Gallery",
|
"images": "Galeriya",
|
||||||
"guestbook": "Guestbook"
|
"guestbook": "Libro sa bisita"
|
||||||
},
|
},
|
||||||
"values": {
|
"values": {
|
||||||
"bool": {
|
"bool": {
|
||||||
@@ -71,7 +76,8 @@
|
|||||||
"hideInput": "Tagoa ang bag-ong entry",
|
"hideInput": "Tagoa ang bag-ong entry",
|
||||||
"imageUpload": "Hulagway",
|
"imageUpload": "Hulagway",
|
||||||
"submit": "Ipadala ang entry",
|
"submit": "Ipadala ang entry",
|
||||||
"noEntries": "Walay entry nga nakit-an"
|
"noEntries": "Walay entry nga nakit-an",
|
||||||
|
"entryImageAlt": "Hulagway sa entry sa libro sa bisita"
|
||||||
},
|
},
|
||||||
"interestedInGender": "Interesado sa",
|
"interestedInGender": "Interesado sa",
|
||||||
"hasChildren": "Naay mga anak",
|
"hasChildren": "Naay mga anak",
|
||||||
@@ -80,7 +86,7 @@
|
|||||||
"willChildren": "Gusto og anak",
|
"willChildren": "Gusto og anak",
|
||||||
"sexualpreference": "Sekswal nga oryentasyon",
|
"sexualpreference": "Sekswal nga oryentasyon",
|
||||||
"language": "Pinulongan",
|
"language": "Pinulongan",
|
||||||
"gender": "Gender",
|
"gender": "Sekso",
|
||||||
"birthdate": "Petsa sa pagkatawo",
|
"birthdate": "Petsa sa pagkatawo",
|
||||||
"age": "Edad",
|
"age": "Edad",
|
||||||
"town": "Lungsod",
|
"town": "Lungsod",
|
||||||
@@ -88,7 +94,9 @@
|
|||||||
"weight": "Timbang"
|
"weight": "Timbang"
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
"title": "Gallery",
|
"kicker": "Mga hulagway ug folder",
|
||||||
|
"intro": "Organisaha ang imong kaugalingong sulod, kontrola ang makita, ug ihan-ay sa mga folder.",
|
||||||
|
"title": "Galeriya",
|
||||||
"folders": "Mga folder",
|
"folders": "Mga folder",
|
||||||
"create_folder": "Paghimo og folder",
|
"create_folder": "Paghimo og folder",
|
||||||
"upload": {
|
"upload": {
|
||||||
@@ -130,19 +138,26 @@
|
|||||||
},
|
},
|
||||||
"show_image_dialog": {
|
"show_image_dialog": {
|
||||||
"title": "Hulagway"
|
"title": "Hulagway"
|
||||||
}
|
},
|
||||||
|
"imagePreviewAlt": "Preview sa hulagway",
|
||||||
|
"imageLoadingAlt": "Nag-load ang hulagway"
|
||||||
},
|
},
|
||||||
"guestbook": {
|
"guestbook": {
|
||||||
"title": "Guestbook",
|
"kicker": "Libro sa bisita",
|
||||||
|
"intro": "Mga mensahe, feedback ug gagmayng hulagway sa imong network.",
|
||||||
|
"title": "Libro sa bisita",
|
||||||
"prevPage": "Balik",
|
"prevPage": "Balik",
|
||||||
"nextPage": "Sunod",
|
"nextPage": "Sunod",
|
||||||
"page": "Panid"
|
"page": "Panid"
|
||||||
},
|
},
|
||||||
"diary": {
|
"diary": {
|
||||||
"title": "Diary",
|
"kicker": "Personal nga mga entry",
|
||||||
"noEntries": "Wala ka pay nahimong diary entries.",
|
"intro": "Mga hunahuna, nota ug mubo nga update sa usa ka malinawon ug personal nga tan-aw.",
|
||||||
"newEntry": "Bag-ong diary entry",
|
"placeholder": "Isulat dinhi ang imong entry sa talaarawan...",
|
||||||
"editEntry": "Usba ang diary entry",
|
"title": "Talaarawan",
|
||||||
|
"noEntries": "Wala ka pay mga entry sa talaarawan.",
|
||||||
|
"newEntry": "Bag-ong entry sa talaarawan",
|
||||||
|
"editEntry": "Usba ang entry sa talaarawan",
|
||||||
"save": "I-save",
|
"save": "I-save",
|
||||||
"update": "I-update",
|
"update": "I-update",
|
||||||
"cancel": "I-cancel",
|
"cancel": "I-cancel",
|
||||||
@@ -154,12 +169,22 @@
|
|||||||
"page": "Panid"
|
"page": "Panid"
|
||||||
},
|
},
|
||||||
"forum": {
|
"forum": {
|
||||||
|
"kicker": "Forum sa komunidad",
|
||||||
|
"intro": "Mga hilisgutan, diskusyon ug bag-ong post sa usa ka organisado nga lugar.",
|
||||||
|
"createTitle": "Paghimo og bag-ong hilisgutan",
|
||||||
|
"createIntro": "Ibutang una ang titulo, dayon isulat ang post ug dayon i-publish dayon.",
|
||||||
|
"cancelCreation": "Kanselahon",
|
||||||
|
"creationHint": "Kinahanglan mapun-an ang titulo ug sulod.",
|
||||||
|
"communityFallback": "Komunidad",
|
||||||
|
"topicIntro": "Mga diskusyon, tubag ug bag-ong post sa usa ka mas nakapokus nga pagbasa.",
|
||||||
|
"topicCreated": "Malampuson nga nahimo ang hilisgutan.",
|
||||||
|
"topicCreateError": "Adunay sayop sa paghimo sa hilisgutan",
|
||||||
"title": "Forum",
|
"title": "Forum",
|
||||||
"showNewTopic": "Paghimo og bag-ong topic",
|
"showNewTopic": "Paghimo og bag-ong topic",
|
||||||
"hideNewTopic": "I-cancel ang paghimo",
|
"hideNewTopic": "I-cancel ang paghimo",
|
||||||
"noTitles": "Walay topic nga available",
|
"noTitles": "Walay topic nga available",
|
||||||
"topic": "Topic",
|
"topic": "Hilisgutan",
|
||||||
"createNewTopic": "Paghimo og topic",
|
"createNewTopic": "Paghimo og hilisgutan",
|
||||||
"createdBy": "Gihimo ni",
|
"createdBy": "Gihimo ni",
|
||||||
"createdAt": "Gihimo sa",
|
"createdAt": "Gihimo sa",
|
||||||
"reactions": "Reaksiyon",
|
"reactions": "Reaksiyon",
|
||||||
@@ -190,7 +215,174 @@
|
|||||||
"lockedShort": "Magamit ra kini nga area human sa moderator approval.",
|
"lockedShort": "Magamit ra kini nga area human sa moderator approval.",
|
||||||
"requestVerification": "Mangayo og access",
|
"requestVerification": "Mangayo og access",
|
||||||
"requestSent": "Napadala na ang access request.",
|
"requestSent": "Napadala na ang access request.",
|
||||||
"requestError": "Wala mapadala ang access request."
|
"requestError": "Wala mapadala ang access request.",
|
||||||
|
"picturesTitle": "Erotic nga mga hulagway",
|
||||||
|
"picturesIntro": "Ang imong mga sulod magpabiling bulag gikan sa kasagarang galeriya. Dinhi nimo dumalahon ang mga hulagway para sa naablihang erotic area.",
|
||||||
|
"uploadTitle": "Pag-upload og erotic nga hulagway",
|
||||||
|
"noimages": "Wala pay hulagway niining erotic nga folder.",
|
||||||
|
"videosTitle": "Erotic nga mga video",
|
||||||
|
"videosIntro": "Ang imong kaugalingong mga video dumalahon nga bulag sa normal nga social area. Dinhi nimo organisahon ang uploads, visibility ug moderation status sa usa ka lugar.",
|
||||||
|
"videoUploadTitle": "Pag-upload og erotic nga video",
|
||||||
|
"videoUploadHint": "Pag-upload dinhi og mga video para sa imong naablihang erotic area ug pun-a dayon ang titulo ug deskripsiyon.",
|
||||||
|
"videoDescription": "Deskripsiyon",
|
||||||
|
"videoFile": "File sa video",
|
||||||
|
"videoFormats": "MP4, WEBM, OGG, MOV",
|
||||||
|
"myVideos": "Akong mga video",
|
||||||
|
"sharedVideos": "Mga gi-share nga video",
|
||||||
|
"foreignVideosIntro": "Mga gi-share nga video gikan sa adult area.",
|
||||||
|
"foreignVideosOnlyHint": "Dinhi makita lang nimo ang mga video nga gi-share para nimo sa adult area.",
|
||||||
|
"sharedVideosIntro": "Makita nga mga video gikan sa gi-share nga adult areas.",
|
||||||
|
"noSharedVideos": "Wala pay gi-share nga mga video para nimo karon.",
|
||||||
|
"libraryTitle": "Bibliyoteka",
|
||||||
|
"libraryIntro": "Ang imong uploads, visibility ug reports sa usa ka lugar.",
|
||||||
|
"libraryEmptyHint": "Paghimo una sa wala ang imong unang video ug dayon dumalaha kini dinhi sa bibliyoteka.",
|
||||||
|
"latestUpload": "Pinakabag-ong upload",
|
||||||
|
"visibleVideos": "Makita nga mga video",
|
||||||
|
"moderationCases": "Mga kaso sa moderation",
|
||||||
|
"notesTitle": "Mga pahimangno",
|
||||||
|
"friendsVisibilityHint": "Makakita ra ang mga higala sa sulod kung hamtong sila ug naablihan para sa adult area.",
|
||||||
|
"selectedUsersVisibilityHint": "Ang mga tawo nga espesipikong gi-share-an kinahanglan usab nga hamtong ug naablihan.",
|
||||||
|
"selectedUsersPlaceholder": "anna, bert, clara",
|
||||||
|
"imagePreviewAlt": "Preview sa hulagway",
|
||||||
|
"imageLoadingAlt": "Nag-load ang hulagway",
|
||||||
|
"untitled": "Walay titulo",
|
||||||
|
"noUploadYet": "Wala pay upload",
|
||||||
|
"closeEditing": "Isira ang pag-edit",
|
||||||
|
"editVisibility": "Usba ang visibility",
|
||||||
|
"reportAction": "Ireport",
|
||||||
|
"reportHint": "Gamita ang {action} direkta sa maong entry kung ang sulod kinahanglan susihon.",
|
||||||
|
"reportNote": "Mubo nga nota para sa moderation",
|
||||||
|
"submitReport": "Ipadala ang report",
|
||||||
|
"reportSubmitted": "Natala na ang report.",
|
||||||
|
"reportError": "Dili masave ang report.",
|
||||||
|
"moderationHidden": "Gitago sa moderation",
|
||||||
|
"hiddenByModeration": "Kini nga sulod temporaryong gitago sa moderation.",
|
||||||
|
"reportReasons": {
|
||||||
|
"suspected_minor": "Suspetsang menor de edad",
|
||||||
|
"non_consensual": "Sulod nga walay pagtugot",
|
||||||
|
"violence": "Kapintas o abuso",
|
||||||
|
"harassment": "Paghasol o pagpugos",
|
||||||
|
"spam": "Spam o scam",
|
||||||
|
"other": "Uban pa"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vocab": {
|
||||||
|
"heroEyebrow": "Pagtuon og pinulongan",
|
||||||
|
"summaryTotalLabel": "Tanan nga pinulongan",
|
||||||
|
"summaryTotalIntro": "Tanang aktibong pinulongan nga lugar diin naggamit o nagdumala ka og sulod.",
|
||||||
|
"summaryOwnedLabel": "Imong kaugalingong lugar",
|
||||||
|
"summaryOwnedIntro": "Dinhi ka mismo maghimo og sulod, kapitulo ug materyal sa pagkat-on.",
|
||||||
|
"summarySubscribedLabel": "Gi-subscribe",
|
||||||
|
"summarySubscribedIntro": "Kini nga mga lugar mas para sa pagkat-on ug progreso kaysa pagdumala.",
|
||||||
|
"taskCreateEyebrow": "Paspas nga sugod",
|
||||||
|
"taskCreateTitle": "Paghimo og bag-ong pinulongan",
|
||||||
|
"taskCreateIntro": "Mao kini ang labing maayong sugod kung gusto nimo nga ikaw mismo ang mohikay ug moatiman sa sulod.",
|
||||||
|
"taskContinueEyebrow": "Padayon sa pagkat-on",
|
||||||
|
"taskContinueTitle": "Ablihi ang mga kurso ug kapitulo",
|
||||||
|
"taskContinueIntro": "Sulod dayon sa mga andam nang learning path ug ipadayon ang anaa nang mga kurso.",
|
||||||
|
"ownedSectionTitle": "Imong mga pinulongan",
|
||||||
|
"ownedSectionIntro": "Direktang agi-anan sa pag-edit, mga kapitulo ug pagdumala sa kurso.",
|
||||||
|
"ownedHint": "Pagdumala ug pag-atiman sa sulod",
|
||||||
|
"ownedEmpty": "Wala pay imong kaugalingong pinulongan nga lugar.",
|
||||||
|
"subscribedSectionTitle": "Mga gi-subscribe nga pinulongan",
|
||||||
|
"subscribedSectionIntro": "Maayo para sa paspas nga pagbalik sa pagkat-on nga walay kabug-at sa pagdumala.",
|
||||||
|
"subscribedHint": "Pagkat-on, praktis ug tan-awa ang progreso",
|
||||||
|
"subscribedEmpty": "Walay gi-subscribe nga pinulongan karon.",
|
||||||
|
"languageHeroEyebrow": "Pinulongan",
|
||||||
|
"languageHeroIntro": "Mga kapitulo, pagpangita ug pagpaambit para niining pinulongana sa usa ka lugar.",
|
||||||
|
"newLanguageHeroEyebrow": "Trainer sa bokabularyo",
|
||||||
|
"newLanguageHeroIntro": "Paghimo og bag-ong pinulongan, paghimo og share code ug dayon balhin dayon sa pag-edit.",
|
||||||
|
"newLanguageNameHint": "Sapat na ang mubo ug klaro nga ngalan sa pinulongan para sa pagsugod.",
|
||||||
|
"newLanguageNameValidation": "Ang ngalan kinahanglan adunay labing menos 2 ka karakter.",
|
||||||
|
"subscribeHeroEyebrow": "Trainer sa bokabularyo",
|
||||||
|
"chapterHeroEyebrow": "Trainer sa bokabularyo",
|
||||||
|
"chapterHeroIntro": "Susihon ang sulod sa kapitulo, atimana ang bokabularyo ug balhin dayon sa praktis.",
|
||||||
|
"courses": {
|
||||||
|
"courseKicker": "Kurso sa pagkat-on",
|
||||||
|
"courseListKicker": "Mga kurso",
|
||||||
|
"courseListIntro": "Sal-a ang publiko ug imong kaugalingong mga kurso sa pagkat-on, pangitaa ang angay, ug ipadayon dayon.",
|
||||||
|
"courseShareCodePlaceholder": "pananglitan abc123def456",
|
||||||
|
"courseFlowEyebrow": "Agos sa adlaw",
|
||||||
|
"courseFlowTitle": "Maayong ipadayon karon",
|
||||||
|
"courseFlowIntro": "Ang sunod-sunod mosunod sa konsepto: una ang angay nang balikon, dayon ang kasamtangang block, unya ang intensive phase ug sa katapusan ang libre nga pagpalalom.",
|
||||||
|
"courseFlowReviewStat": "Angay balikon: {count}",
|
||||||
|
"courseFlowBlockStat": "Aktibong block: {block}",
|
||||||
|
"courseFlowReviewTitle": "Angay nga balik-balikon",
|
||||||
|
"courseFlowReviewDescription": "Mga leksiyon nga nahuman na ug angay balikon karong adlawa.",
|
||||||
|
"courseFlowReviewEmpty": "Walay daang leksiyon nga nakamarka isip angay balikon karon.",
|
||||||
|
"courseFlowBlockTitle": "Kasamtangang block",
|
||||||
|
"courseFlowBlockDescription": "Dinhi nahimutang ang sunod regular nga lakang sa kurso.",
|
||||||
|
"courseFlowBlockEmpty": "Nahuman na ang kasamtangang block o walay abli nga leksiyon niini karon.",
|
||||||
|
"courseFlowIntensiveTitle": "Angay nga intensive phase",
|
||||||
|
"courseFlowIntensiveDescription": "Mas dikit nga balik-balik sa dihang lig-on na ang nag-una nga block.",
|
||||||
|
"courseFlowIntensiveEmpty": "Wala pay bag-ong intensive phase nga naablihan karon.",
|
||||||
|
"courseFlowPracticeTitle": "Libre nga pagpalalom",
|
||||||
|
"courseFlowPracticeDescription": "Nahuman nang mga leksiyon para sa hinay-hinay nga dugang praktis gawas sa obligadong agi-anan.",
|
||||||
|
"courseFlowPracticeEmpty": "Sa dihang makahuman ka sa unang mga leksiyon, makita sila dinhi para sa libre nga praktis.",
|
||||||
|
"practiceInTrainer": "Praktisa sa trainer",
|
||||||
|
"lessonsCount": "{count} ka leksiyon",
|
||||||
|
"lessonBlockLabel": "Block {number}",
|
||||||
|
"lessonIntensiveBadge": "Intensive nga balik-balik",
|
||||||
|
"addLessonValidation": "Palihog kompletoha ang numero, titulo ug kapitulo.",
|
||||||
|
"addLessonSuccess": "Malampuson nga nahimo ang leksiyon.",
|
||||||
|
"addLessonError": "Dili madugang ang leksiyon.",
|
||||||
|
"createCourseError": "Dili mahimo ang paghimo sa kurso.",
|
||||||
|
"deleteLessonTitle": "Tangtanga ang leksiyon",
|
||||||
|
"deleteLessonSuccess": "Malampuson nga natangtang ang leksiyon.",
|
||||||
|
"deleteLessonError": "Dili matangtang ang leksiyon.",
|
||||||
|
"enrollCourseError": "Dili makapa-enroll sa kurso.",
|
||||||
|
"editLessonPending": "Ang tagsa-tagsa nga pag-edit sa mga leksiyon moabot pa.",
|
||||||
|
"timeToday": "karon",
|
||||||
|
"timeSinceOneDay": "sukad 1 ka adlaw",
|
||||||
|
"timeSinceDays": "sukad {count} ka adlaw",
|
||||||
|
"reviewDueNow": "angay na karon",
|
||||||
|
"reviewDueTomorrow": "angay ugma",
|
||||||
|
"reviewDueInDays": "angay sulod sa {count} ka adlaw",
|
||||||
|
"reviewDueToday": "angay karon",
|
||||||
|
"reviewDueSinceOneDay": "angay na sukad 1 ka adlaw",
|
||||||
|
"reviewDueSinceDays": "angay na sukad {count} ka adlaw",
|
||||||
|
"reviewStageDay1": "Adlaw 1",
|
||||||
|
"reviewStageDay3": "Adlaw 3",
|
||||||
|
"reviewStageDay7": "Adlaw 7",
|
||||||
|
"reviewStageCompleted": "Nahuman ang review",
|
||||||
|
"phaseQuickstart": "Paspas nga sugod",
|
||||||
|
"phaseDailyLife": "Adlaw-adlaw",
|
||||||
|
"phaseStabilization": "Pagpalig-on",
|
||||||
|
"phaseDefault": "Hugna sa pagkat-on",
|
||||||
|
"didacticModeCoreInput": "Bag-ong sulod",
|
||||||
|
"didacticModeGuidedDialogue": "Giyahang dayalogo",
|
||||||
|
"didacticModeContrastTraining": "Contrast training",
|
||||||
|
"didacticModePatternDrill": "Pagpraktis sa mga hulma",
|
||||||
|
"didacticModeRealLifeScenario": "Tinuod nga kahimtang sa adlaw-adlaw",
|
||||||
|
"didacticModeIntensiveReview": "Hugna sa balik-balik",
|
||||||
|
"didacticModeCheckpoint": "Checkpoint",
|
||||||
|
"didacticModeDefault": "Yunit sa pagkat-on",
|
||||||
|
"didacticModeFocusDefault": "Pokus sa pagkat-on",
|
||||||
|
"lessonMetaFocus": "Pokus",
|
||||||
|
"lessonMetaPhase": "Hugna",
|
||||||
|
"lessonMetaNewUnits": "Bag-ong yunit",
|
||||||
|
"lessonMetaReview": "Balik-balik",
|
||||||
|
"intensiveReviewTitle": "Intensive nga hugna sa balik-balik",
|
||||||
|
"intensiveReviewIntro": "Kini nga leksiyon nag-una sa balik-balik ug pagpalalom. Gipakunhod og tinuyo ang bag-ong sulod aron malig-on ang mga hulma nga nakat-onan na.",
|
||||||
|
"reviewPriorityTitle": "Hinay-hinay nga gisagol ang balik-balik",
|
||||||
|
"reviewPriorityIntro": "Sa sinugdan, ang pokus anaa sa bag-ong mga pulong niining leksiyona. Samtang mopadayon ka, hinay-hinay nga masagol ang daan nga bokabularyo.",
|
||||||
|
"exerciseLockTitle": "Naka-lock pa ang chapter test",
|
||||||
|
"trainerStartWithReview": "Sugdi sa bag-ong bokabularyo niining leksiyona. Samtang nagpraktis ka, awtomatikong isagol sa trainer ang angay nga balik-balik.",
|
||||||
|
"startLesson": "Sugdi ang leksiyon",
|
||||||
|
"trainerProgressNewContent": "Bag-ong sulod: {current}/{target}",
|
||||||
|
"trainerProgressReview": "Balik-balik: {count}",
|
||||||
|
"trainerProgressMixShare": "Nasagol nga bahin: {percent}%",
|
||||||
|
"unknownExerciseTypeNotice": "Kini nga matang sa ehersisyo wala pa ipakita nga interaktibo sa kasamtangang view.",
|
||||||
|
"unknownExerciseTypeLabel": "Matang: {type}",
|
||||||
|
"lessonReviewHeadlineDone": "Nakaabot na kini nga leksiyon sa libre nga pagpalalom.",
|
||||||
|
"lessonReviewHeadlineDue": "Angay na karon kining review wave.",
|
||||||
|
"lessonReviewHeadlineScheduled": "Gitakda kini nga leksiyon para sa sunod nga review wave.",
|
||||||
|
"lessonReviewHintDone": "Nahuman na ang 1/3/7 ka adlaw nga balik-balik. Mahimo na nimo kining praktison sa mas luag nga paagi.",
|
||||||
|
"lessonReviewHintNextDue": "Sunod nga petsa: {due}.",
|
||||||
|
"reviewTimeNow": "karon",
|
||||||
|
"reviewTimeTomorrow": "ugma",
|
||||||
|
"reviewTimeInDays": "sulod sa {count} ka adlaw"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -461,7 +461,9 @@
|
|||||||
"selectStockType": "Lagertyp auswählen",
|
"selectStockType": "Lagertyp auswählen",
|
||||||
"costPerUnit": "Kosten pro Einheit",
|
"costPerUnit": "Kosten pro Einheit",
|
||||||
"buycost": "Kosten",
|
"buycost": "Kosten",
|
||||||
"sellincome": "Einnahmen"
|
"sellincome": "Einnahmen",
|
||||||
|
"buyPartialError": "Fehler beim Kaufen eines Teils der Lagerkapazität.",
|
||||||
|
"sellError": "Fehler beim Verkaufen der Lagerkapazität."
|
||||||
},
|
},
|
||||||
"vehicles": {
|
"vehicles": {
|
||||||
"cargo_cart": "Lastkarren",
|
"cargo_cart": "Lastkarren",
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"friends": {
|
"friends": {
|
||||||
|
"kicker": "Community",
|
||||||
|
"intro": "Freundschaften, offene Anfragen und laufende Kontakte an einem Ort.",
|
||||||
"title": "Freunde",
|
"title": "Freunde",
|
||||||
|
"stats": {
|
||||||
|
"existing": "Bestehend",
|
||||||
|
"open": "Offen"
|
||||||
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"existing": "Bestehende",
|
"existing": "Bestehende",
|
||||||
"rejected": "Abgelehnte",
|
"rejected": "Abgelehnte",
|
||||||
|
|||||||
@@ -41,6 +41,59 @@
|
|||||||
"message": {
|
"message": {
|
||||||
"close": "Schließen"
|
"close": "Schließen"
|
||||||
},
|
},
|
||||||
|
"appShell": {
|
||||||
|
"header": {
|
||||||
|
"tagline": "Community-Plattform",
|
||||||
|
"beta": "Beta",
|
||||||
|
"backend": "Backend",
|
||||||
|
"daemon": "Daemon",
|
||||||
|
"language": "Sprache"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"systemLabel": "System",
|
||||||
|
"noOpenDialogs": "Keine offenen Dialoge",
|
||||||
|
"activeWindows": "{count} Fenster aktiv",
|
||||||
|
"systemReady": "System bereit",
|
||||||
|
"systemStatusUnavailable": "Der Systemstatus ist in dieser Ansicht derzeit nicht direkt verfügbar."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"widgets": {
|
||||||
|
"dashboard": {
|
||||||
|
"dragHandle": "Verschieben",
|
||||||
|
"loading": "Laden..."
|
||||||
|
},
|
||||||
|
"birthdays": {
|
||||||
|
"today": "Heute!",
|
||||||
|
"tomorrow": "Morgen",
|
||||||
|
"turningAge": "(wird {age})",
|
||||||
|
"inDays": "{count} Tage",
|
||||||
|
"empty": "Keine Geburtstage von Freunden sichtbar"
|
||||||
|
},
|
||||||
|
"upcoming": {
|
||||||
|
"today": "Heute",
|
||||||
|
"tomorrow": "Morgen",
|
||||||
|
"timeAt": "{time} Uhr",
|
||||||
|
"allDay": "Ganztägig",
|
||||||
|
"empty": "Keine anstehenden Termine"
|
||||||
|
},
|
||||||
|
"appointments": {
|
||||||
|
"title": "📅 Termine",
|
||||||
|
"loading": "Lade Termine...",
|
||||||
|
"empty": "Keine bevorstehenden Termine",
|
||||||
|
"loadError": "Termine konnten nicht geladen werden"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"noEntries": "Keine Einträge",
|
||||||
|
"entriesCount": "({count} Einträge)",
|
||||||
|
"fieldsCount": "({count} Felder)"
|
||||||
|
},
|
||||||
|
"news": {
|
||||||
|
"emptyValue": "—"
|
||||||
|
},
|
||||||
|
"falukant": {
|
||||||
|
"emptyValue": "—"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"male": "Männlich",
|
"male": "Männlich",
|
||||||
"female": "Weiblich",
|
"female": "Weiblich",
|
||||||
|
|||||||
@@ -1,5 +1,54 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
|
"dashboard": {
|
||||||
|
"kicker": "Dein Bereich",
|
||||||
|
"title": "Willkommen zurück!",
|
||||||
|
"subtitle": "Dein persönlicher Einstieg in Community, Termine, Falukant und laufende Aktivitäten.",
|
||||||
|
"edit": "Dashboard bearbeiten",
|
||||||
|
"addWidget": "+ Widget hinzufügen ...",
|
||||||
|
"addAgain": "Nochmal hinzufügen",
|
||||||
|
"done": "Fertig",
|
||||||
|
"sectionTitle": "Deine Übersicht",
|
||||||
|
"sectionIntro": "Widgets lassen sich verschieben und im Bearbeitungsmodus anpassen.",
|
||||||
|
"widgetTitlePlaceholder": "Titel",
|
||||||
|
"removeWidget": "Widget entfernen",
|
||||||
|
"remove": "Entfernen",
|
||||||
|
"empty": "Noch keine Widgets. Klicke auf „Dashboard bearbeiten“ und dann „+ Widget hinzufügen“.",
|
||||||
|
"defaultAppointmentsWidget": "Termine",
|
||||||
|
"loadError": "Dashboard konnte nicht geladen werden.",
|
||||||
|
"saveError": "Dashboard konnte nicht gespeichert werden.",
|
||||||
|
"widgetLabels": {
|
||||||
|
"appointments": "Termine",
|
||||||
|
"falukant": "Falukant",
|
||||||
|
"news": "News",
|
||||||
|
"birthdays": "Geburtstage",
|
||||||
|
"upcoming": "Nächste Termine",
|
||||||
|
"calendar": "Kalender"
|
||||||
|
},
|
||||||
|
"overview": {
|
||||||
|
"activeWidgetsLabel": "Aktive Widgets",
|
||||||
|
"activeWidgetsText": "Dein Dashboard ist modular aufgebaut und kann jederzeit umsortiert werden.",
|
||||||
|
"availableModulesLabel": "Verfügbare Module",
|
||||||
|
"availableModulesText": "Du kannst Community-, Kalender-, News- und Falukant-Module kombinieren.",
|
||||||
|
"editModeLabel": "Bearbeitungsmodus",
|
||||||
|
"editModeActive": "Aktiv",
|
||||||
|
"editModeInactive": "Aus",
|
||||||
|
"editModeActiveText": "Widgets können gerade ergänzt und angepasst werden.",
|
||||||
|
"editModeInactiveText": "Inhalte bleiben fokussiert und ruhig lesbar."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vocabLanding": {
|
||||||
|
"eyebrow": "Sprachen online lernen",
|
||||||
|
"title": "Der Vokabeltrainer auf YourPart kombiniert Lernen, Kurse und Übungen in einer Plattform.",
|
||||||
|
"lead": "Arbeite mit interaktiven Lektionen, erweitere deinen Wortschatz und nutze strukturierte Inhalte für einen motivierenden Lernfluss direkt im Browser.",
|
||||||
|
"cta": "Kostenlos starten",
|
||||||
|
"feature1Title": "Interaktive Kurse",
|
||||||
|
"feature1Text": "Kurse, Lektionen und Übungen helfen beim systematischen Aufbau neuer Sprachkenntnisse.",
|
||||||
|
"feature2Title": "Praxisorientiert",
|
||||||
|
"feature2Text": "Wortschatz, Grammatik und Wiederholung werden auf eine alltagstaugliche Lernroutine ausgerichtet.",
|
||||||
|
"feature3Title": "Teil einer Community",
|
||||||
|
"feature3Text": "Der Sprachbereich ist in eine größere Community-Plattform mit Blogs, Forum und Chat eingebettet."
|
||||||
|
},
|
||||||
"betaNoticeLabel": "Beta-Hinweis:",
|
"betaNoticeLabel": "Beta-Hinweis:",
|
||||||
"betaNoticeText": "YourPart befindet sich in aktiver Entwicklung. Funktionen können unvollständig sein, Inhalte fehlen noch und es kann zu Änderungen kommen.",
|
"betaNoticeText": "YourPart befindet sich in aktiver Entwicklung. Funktionen können unvollständig sein, Inhalte fehlen noch und es kann zu Änderungen kommen.",
|
||||||
"nologin": {
|
"nologin": {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"personal": {
|
"personal": {
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"title": "Kalender",
|
"title": "Kalender",
|
||||||
|
"kicker": "Planung",
|
||||||
|
"intro": "Termine, Geburtstage und eigene Einträge in einer strukturierten Übersicht.",
|
||||||
"today": "Heute",
|
"today": "Heute",
|
||||||
"newEntry": "Neuer Eintrag",
|
"newEntry": "Neuer Eintrag",
|
||||||
"editEntry": "Eintrag bearbeiten",
|
"editEntry": "Eintrag bearbeiten",
|
||||||
|
|||||||
@@ -57,7 +57,8 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
"en": "Englisch",
|
"en": "Englisch",
|
||||||
"ceb": "Bisaya"
|
"ceb": "Bisaya",
|
||||||
|
"es": "Spanisch"
|
||||||
},
|
},
|
||||||
"eyecolor": {
|
"eyecolor": {
|
||||||
"blue": "Blau",
|
"blue": "Blau",
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
"socialnetwork": {
|
"socialnetwork": {
|
||||||
"usersearch": {
|
"usersearch": {
|
||||||
|
"kicker": "Community-Suche",
|
||||||
|
"intro": "Mit Namen, Alter und Geschlecht gezielt passende Kontakte in der Community finden.",
|
||||||
|
"ageSeparator": "bis",
|
||||||
|
"resultsCount": "{count} Treffer",
|
||||||
|
"openProfile": "Profil öffnen",
|
||||||
"title": "Benutzersuche",
|
"title": "Benutzersuche",
|
||||||
"username": "Benutzername",
|
"username": "Benutzername",
|
||||||
"age_from": "Alter von",
|
"age_from": "Alter von",
|
||||||
@@ -120,7 +125,8 @@
|
|||||||
"hideInput": "Neuer Eintrag verbergen",
|
"hideInput": "Neuer Eintrag verbergen",
|
||||||
"imageUpload": "Bild",
|
"imageUpload": "Bild",
|
||||||
"submit": "Eintrag absenden",
|
"submit": "Eintrag absenden",
|
||||||
"noEntries": "Keine Einträge gefunden"
|
"noEntries": "Keine Einträge gefunden",
|
||||||
|
"entryImageAlt": "Bild zum Gästebucheintrag"
|
||||||
},
|
},
|
||||||
"interestedInGender": "Interessiert an",
|
"interestedInGender": "Interessiert an",
|
||||||
"hasChildren": "Hat Kinder",
|
"hasChildren": "Hat Kinder",
|
||||||
@@ -147,6 +153,8 @@
|
|||||||
"weight": "Gewicht"
|
"weight": "Gewicht"
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
|
"kicker": "Bilder und Ordner",
|
||||||
|
"intro": "Eigene Inhalte organisieren, sichtbar machen und in Ordnern strukturieren.",
|
||||||
"title": "Gallerie",
|
"title": "Gallerie",
|
||||||
"folders": "Ordner",
|
"folders": "Ordner",
|
||||||
"create_folder": "Ordner anlegen",
|
"create_folder": "Ordner anlegen",
|
||||||
@@ -189,15 +197,22 @@
|
|||||||
},
|
},
|
||||||
"show_image_dialog": {
|
"show_image_dialog": {
|
||||||
"title": "Bild"
|
"title": "Bild"
|
||||||
}
|
},
|
||||||
|
"imagePreviewAlt": "Bildvorschau",
|
||||||
|
"imageLoadingAlt": "Bild wird geladen"
|
||||||
},
|
},
|
||||||
"guestbook": {
|
"guestbook": {
|
||||||
|
"kicker": "Gästebuch",
|
||||||
|
"intro": "Nachrichten, Rückmeldungen und kleine Einblicke aus deinem Netzwerk.",
|
||||||
"title": "Gästebuch",
|
"title": "Gästebuch",
|
||||||
"prevPage": "Zurück",
|
"prevPage": "Zurück",
|
||||||
"nextPage": "Weiter",
|
"nextPage": "Weiter",
|
||||||
"page": "Seite"
|
"page": "Seite"
|
||||||
},
|
},
|
||||||
"diary": {
|
"diary": {
|
||||||
|
"kicker": "Persönliche Einträge",
|
||||||
|
"intro": "Gedanken, Notizen und kurze Updates in einer ruhigen, persönlichen Ansicht.",
|
||||||
|
"placeholder": "Schreibe deinen Tagebucheintrag...",
|
||||||
"title": "Tagebuch",
|
"title": "Tagebuch",
|
||||||
"noEntries": "Du hast noch keine Tagebucheinträge gemacht.",
|
"noEntries": "Du hast noch keine Tagebucheinträge gemacht.",
|
||||||
"newEntry": "Neuer Tagebucheintrag",
|
"newEntry": "Neuer Tagebucheintrag",
|
||||||
@@ -213,6 +228,16 @@
|
|||||||
"page": "Seite"
|
"page": "Seite"
|
||||||
},
|
},
|
||||||
"forum": {
|
"forum": {
|
||||||
|
"kicker": "Community-Forum",
|
||||||
|
"intro": "Themen, Diskussionen und neue Beiträge an einem strukturierten Ort.",
|
||||||
|
"createTitle": "Neues Thema verfassen",
|
||||||
|
"createIntro": "Erst Titel setzen, dann den Beitrag schreiben und anschließend direkt veröffentlichen.",
|
||||||
|
"cancelCreation": "Abbrechen",
|
||||||
|
"creationHint": "Titel und Inhalt müssen beide ausgefüllt sein.",
|
||||||
|
"communityFallback": "Community",
|
||||||
|
"topicIntro": "Diskussionen, Antworten und neue Beiträge in einer fokussierten Lesefläche.",
|
||||||
|
"topicCreated": "Thema erfolgreich erstellt.",
|
||||||
|
"topicCreateError": "Fehler beim Erstellen des Themas",
|
||||||
"title": "Forum",
|
"title": "Forum",
|
||||||
"showNewTopic": "Neues Thema erstellen",
|
"showNewTopic": "Neues Thema erstellen",
|
||||||
"hideNewTopic": "Erstellen unterbrechen",
|
"hideNewTopic": "Erstellen unterbrechen",
|
||||||
@@ -277,9 +302,32 @@
|
|||||||
"videoUploadHint": "Lade hier Videos für deinen freigeschalteten Erotikbereich hoch und pflege Titel sowie Beschreibung direkt beim Upload.",
|
"videoUploadHint": "Lade hier Videos für deinen freigeschalteten Erotikbereich hoch und pflege Titel sowie Beschreibung direkt beim Upload.",
|
||||||
"videoDescription": "Beschreibung",
|
"videoDescription": "Beschreibung",
|
||||||
"videoFile": "Videodatei",
|
"videoFile": "Videodatei",
|
||||||
|
"videoFormats": "MP4, WEBM, OGG, MOV",
|
||||||
"myVideos": "Meine Videos",
|
"myVideos": "Meine Videos",
|
||||||
|
"sharedVideos": "Freigegebene Videos",
|
||||||
|
"foreignVideosIntro": "Freigegebene Videos aus dem Erwachsenenbereich.",
|
||||||
|
"foreignVideosOnlyHint": "Du siehst hier nur Videos, die dir für den Erwachsenenbereich freigegeben wurden.",
|
||||||
|
"sharedVideosIntro": "Sichtbare Videos aus freigegebenen Erwachsenenbereichen.",
|
||||||
|
"noSharedVideos": "Für dich sind aktuell keine freigegebenen Videos vorhanden.",
|
||||||
|
"libraryTitle": "Bibliothek",
|
||||||
|
"libraryIntro": "Eigene Uploads, Freigaben und Meldungen an einem Ort.",
|
||||||
|
"libraryEmptyHint": "Lege links dein erstes Video an und verwalte es danach hier in der Bibliothek.",
|
||||||
|
"latestUpload": "Letzter Upload",
|
||||||
|
"visibleVideos": "Sichtbare Videos",
|
||||||
|
"moderationCases": "Moderationsfälle",
|
||||||
|
"notesTitle": "Hinweise",
|
||||||
|
"friendsVisibilityHint": "Freunde sehen Inhalte nur dann, wenn sie volljährig und für den Erwachsenenbereich freigeschaltet sind.",
|
||||||
|
"selectedUsersVisibilityHint": "Gezielt freigegebene Personen müssen ebenfalls volljährig und freigeschaltet sein.",
|
||||||
|
"selectedUsersPlaceholder": "anna, bert, clara",
|
||||||
|
"imagePreviewAlt": "Bildvorschau",
|
||||||
|
"imageLoadingAlt": "Bild wird geladen",
|
||||||
|
"untitled": "Ohne Titel",
|
||||||
|
"noUploadYet": "Noch kein Upload",
|
||||||
|
"closeEditing": "Bearbeitung schließen",
|
||||||
|
"editVisibility": "Freigaben bearbeiten",
|
||||||
"noVideos": "Du hast noch keine Erotikvideos hochgeladen.",
|
"noVideos": "Du hast noch keine Erotikvideos hochgeladen.",
|
||||||
"reportAction": "Melden",
|
"reportAction": "Melden",
|
||||||
|
"reportHint": "Nutze {action} direkt am jeweiligen Eintrag, wenn Inhalte geprüft werden sollen.",
|
||||||
"reportNote": "Kurze Notiz für die Moderation",
|
"reportNote": "Kurze Notiz für die Moderation",
|
||||||
"submitReport": "Meldung absenden",
|
"submitReport": "Meldung absenden",
|
||||||
"reportSubmitted": "Die Meldung wurde aufgenommen.",
|
"reportSubmitted": "Die Meldung wurde aufgenommen.",
|
||||||
@@ -323,6 +371,36 @@
|
|||||||
"vocab": {
|
"vocab": {
|
||||||
"title": "Vokabeltrainer",
|
"title": "Vokabeltrainer",
|
||||||
"description": "Lege Sprachen an (oder abonniere sie) und teile sie mit Freunden.",
|
"description": "Lege Sprachen an (oder abonniere sie) und teile sie mit Freunden.",
|
||||||
|
"heroEyebrow": "Sprachenlernen",
|
||||||
|
"summaryTotalLabel": "Sprachen gesamt",
|
||||||
|
"summaryTotalIntro": "Alle aktiven Sprachbereiche, in denen du Inhalte nutzt oder verwaltest.",
|
||||||
|
"summaryOwnedLabel": "Eigene Bereiche",
|
||||||
|
"summaryOwnedIntro": "Hier legst du Inhalte, Kapitel und Lernmaterial aktiv selbst an.",
|
||||||
|
"summarySubscribedLabel": "Abonniert",
|
||||||
|
"summarySubscribedIntro": "Diese Bereiche sind eher für Lernen und Fortschritt statt Verwaltung gedacht.",
|
||||||
|
"taskCreateEyebrow": "Schnellstart",
|
||||||
|
"taskCreateTitle": "Neue Sprache anlegen",
|
||||||
|
"taskCreateIntro": "Der beste Einstieg, wenn du Inhalte selbst strukturieren und pflegen willst.",
|
||||||
|
"taskContinueEyebrow": "Weiterlernen",
|
||||||
|
"taskContinueTitle": "Kurse und Kapitel öffnen",
|
||||||
|
"taskContinueIntro": "Springe direkt in bestehende Lernpfade und arbeite mit vorhandenen Kursen weiter.",
|
||||||
|
"ownedSectionTitle": "Eigene Sprachen",
|
||||||
|
"ownedSectionIntro": "Direkter Einstieg in Bearbeitung, Kapitel und Kursverwaltung.",
|
||||||
|
"ownedHint": "Verwalten und Inhalte pflegen",
|
||||||
|
"ownedEmpty": "Noch keine eigenen Sprachbereiche vorhanden.",
|
||||||
|
"subscribedSectionTitle": "Abonnierte Sprachen",
|
||||||
|
"subscribedSectionIntro": "Gut für schnellen Wiedereinstieg ins Lernen ohne Verwaltungsaufwand.",
|
||||||
|
"subscribedHint": "Lernen, üben und Fortschritt ansehen",
|
||||||
|
"subscribedEmpty": "Keine abonnierten Sprachen vorhanden.",
|
||||||
|
"languageHeroEyebrow": "Sprache",
|
||||||
|
"languageHeroIntro": "Kapitel, Suchfunktionen und Freigaben für diese Sprache an einem Ort.",
|
||||||
|
"newLanguageHeroEyebrow": "Vokabeltrainer",
|
||||||
|
"newLanguageHeroIntro": "Neue Sprache anlegen, Freigabecode erzeugen und direkt in die Bearbeitung wechseln.",
|
||||||
|
"newLanguageNameHint": "Ein kurzer, klarer Sprachname reicht für den Start.",
|
||||||
|
"newLanguageNameValidation": "Der Name sollte mindestens 2 Zeichen haben.",
|
||||||
|
"subscribeHeroEyebrow": "Vokabeltrainer",
|
||||||
|
"chapterHeroEyebrow": "Vokabeltrainer",
|
||||||
|
"chapterHeroIntro": "Kapitelinhalt durchsuchen, Vokabeln pflegen und direkt in die Übung wechseln.",
|
||||||
"newLanguage": "Neue Sprache",
|
"newLanguage": "Neue Sprache",
|
||||||
"newLanguageTitle": "Neue Sprache anlegen",
|
"newLanguageTitle": "Neue Sprache anlegen",
|
||||||
"languageName": "Name der Sprache",
|
"languageName": "Name der Sprache",
|
||||||
@@ -570,7 +648,91 @@
|
|||||||
"languageAssistantPatternHint": "Nutze dabei besonders dieses Muster",
|
"languageAssistantPatternHint": "Nutze dabei besonders dieses Muster",
|
||||||
"languageAssistantPresetPracticeStart": "Lass uns zur Lektion \"{lesson}\" einen kurzen alltagsnahen Dialog üben. Stelle mir bitte Fragen und warte auf meine Antworten.",
|
"languageAssistantPresetPracticeStart": "Lass uns zur Lektion \"{lesson}\" einen kurzen alltagsnahen Dialog üben. Stelle mir bitte Fragen und warte auf meine Antworten.",
|
||||||
"languageAssistantPresetCorrectStart": "Ich möchte eigene Sätze zur Lektion \"{lesson}\" schreiben. Bitte korrigiere meine Antworten knapp und verständlich.",
|
"languageAssistantPresetCorrectStart": "Ich möchte eigene Sätze zur Lektion \"{lesson}\" schreiben. Bitte korrigiere meine Antworten knapp und verständlich.",
|
||||||
"thisLesson": "dieser Lektion"
|
"thisLesson": "dieser Lektion",
|
||||||
|
"courseKicker": "Lernkurs",
|
||||||
|
"courseListKicker": "Kurse",
|
||||||
|
"courseListIntro": "Öffentliche und eigene Lernkurse filtern, finden und direkt weiterlernen.",
|
||||||
|
"courseShareCodePlaceholder": "z. B. abc123def456",
|
||||||
|
"courseFlowEyebrow": "Tagesfluss",
|
||||||
|
"courseFlowTitle": "Heute sinnvoll weitermachen",
|
||||||
|
"courseFlowIntro": "Die Reihenfolge folgt dem Konzept: fällige Wiederholung zuerst, dann aktueller Block, danach Intensivphase und freie Vertiefung.",
|
||||||
|
"courseFlowReviewStat": "Fällige Wiederholung: {count}",
|
||||||
|
"courseFlowBlockStat": "Aktiver Block: {block}",
|
||||||
|
"courseFlowReviewTitle": "Fällige Wiederholung",
|
||||||
|
"courseFlowReviewDescription": "Bereits abgeschlossene Lektionen, die heute wieder drankommen sollten.",
|
||||||
|
"courseFlowReviewEmpty": "Heute ist keine ältere Lektion als fällige Wiederholung markiert.",
|
||||||
|
"courseFlowBlockTitle": "Aktueller Block",
|
||||||
|
"courseFlowBlockDescription": "Hier liegt der nächste reguläre Fortschritt im Kurs.",
|
||||||
|
"courseFlowBlockEmpty": "Der aktuelle Block ist bereits abgeschlossen oder es gibt gerade keine offene Blocklektion.",
|
||||||
|
"courseFlowIntensiveTitle": "Fällige Intensivphase",
|
||||||
|
"courseFlowIntensiveDescription": "Verdichtete Wiederholung, sobald der Block davor weitgehend sitzt.",
|
||||||
|
"courseFlowIntensiveEmpty": "Aktuell ist keine neue Intensivphase freigeschaltet.",
|
||||||
|
"courseFlowPracticeTitle": "Freie Vertiefung",
|
||||||
|
"courseFlowPracticeDescription": "Abgeschlossene Lektionen für lockeres Nachtrainieren außerhalb des Pflichtpfads.",
|
||||||
|
"courseFlowPracticeEmpty": "Sobald du erste Lektionen abgeschlossen hast, erscheinen sie hier für freies Nachtrainieren.",
|
||||||
|
"practiceInTrainer": "Im Trainer üben",
|
||||||
|
"lessonsCount": "{count} Lektionen",
|
||||||
|
"lessonBlockLabel": "Block {number}",
|
||||||
|
"lessonIntensiveBadge": "Intensive Wiederholung",
|
||||||
|
"addLessonValidation": "Bitte Nummer, Titel und Kapitel vollständig angeben.",
|
||||||
|
"addLessonSuccess": "Lektion erfolgreich angelegt.",
|
||||||
|
"addLessonError": "Fehler beim Hinzufügen der Lektion.",
|
||||||
|
"createCourseError": "Fehler beim Erstellen des Kurses.",
|
||||||
|
"deleteLessonTitle": "Lektion löschen",
|
||||||
|
"deleteLessonSuccess": "Lektion erfolgreich gelöscht.",
|
||||||
|
"deleteLessonError": "Fehler beim Löschen der Lektion.",
|
||||||
|
"enrollCourseError": "Fehler beim Einschreiben.",
|
||||||
|
"editLessonPending": "Die Bearbeitung einzelner Lektionen folgt noch.",
|
||||||
|
"timeToday": "heute",
|
||||||
|
"timeSinceOneDay": "seit 1 Tag",
|
||||||
|
"timeSinceDays": "seit {count} Tagen",
|
||||||
|
"reviewDueNow": "jetzt fällig",
|
||||||
|
"reviewDueTomorrow": "morgen fällig",
|
||||||
|
"reviewDueInDays": "in {count} Tagen fällig",
|
||||||
|
"reviewDueToday": "heute fällig",
|
||||||
|
"reviewDueSinceOneDay": "seit 1 Tag fällig",
|
||||||
|
"reviewDueSinceDays": "seit {count} Tagen fällig",
|
||||||
|
"reviewStageDay1": "Tag 1",
|
||||||
|
"reviewStageDay3": "Tag 3",
|
||||||
|
"reviewStageDay7": "Tag 7",
|
||||||
|
"reviewStageCompleted": "Review abgeschlossen",
|
||||||
|
"phaseQuickstart": "Schnellstart",
|
||||||
|
"phaseDailyLife": "Alltag",
|
||||||
|
"phaseStabilization": "Stabilisierung",
|
||||||
|
"phaseDefault": "Lernphase",
|
||||||
|
"didacticModeCoreInput": "Neuer Stoff",
|
||||||
|
"didacticModeGuidedDialogue": "Geführter Dialog",
|
||||||
|
"didacticModeContrastTraining": "Kontrasttraining",
|
||||||
|
"didacticModePatternDrill": "Mustertraining",
|
||||||
|
"didacticModeRealLifeScenario": "Alltagsszenario",
|
||||||
|
"didacticModeIntensiveReview": "Wiederholungsphase",
|
||||||
|
"didacticModeCheckpoint": "Checkpoint",
|
||||||
|
"didacticModeDefault": "Lerneinheit",
|
||||||
|
"didacticModeFocusDefault": "Lernfokus",
|
||||||
|
"lessonMetaFocus": "Fokus",
|
||||||
|
"lessonMetaPhase": "Phase",
|
||||||
|
"lessonMetaNewUnits": "Neue Einheiten",
|
||||||
|
"lessonMetaReview": "Wiederholung",
|
||||||
|
"intensiveReviewTitle": "Intensive Wiederholungsphase",
|
||||||
|
"intensiveReviewIntro": "Diese Lektion priorisiert Wiederholung und Vertiefung. Neuer Stoff wird bewusst reduziert, damit vorhandene Muster stabil werden.",
|
||||||
|
"reviewPriorityTitle": "Wiederholung läuft schrittweise mit",
|
||||||
|
"reviewPriorityIntro": "Zuerst liegt der Fokus auf den neuen Begriffen dieser Lektion. Mit deinem Fortschritt fließen ältere Vokabeln dann zunehmend mit ein.",
|
||||||
|
"exerciseLockTitle": "Kapitel-Prüfung noch gesperrt",
|
||||||
|
"trainerStartWithReview": "Starte mit den neuen Vokabeln dieser Lektion. Mit fortschreitendem Üben mischt der Trainer automatisch passende Wiederholungen ein.",
|
||||||
|
"startLesson": "Lektion starten",
|
||||||
|
"trainerProgressNewContent": "Neue Inhalte: {current}/{target}",
|
||||||
|
"trainerProgressReview": "Wiederholung: {count}",
|
||||||
|
"trainerProgressMixShare": "Mischanteil: {percent}%",
|
||||||
|
"unknownExerciseTypeNotice": "Dieser Übungstyp wird in der aktuellen Ansicht noch nicht interaktiv dargestellt.",
|
||||||
|
"unknownExerciseTypeLabel": "Typ: {type}",
|
||||||
|
"lessonReviewHeadlineDone": "Diese Lektion ist in der freien Vertiefung angekommen.",
|
||||||
|
"lessonReviewHeadlineDue": "Diese Review-Welle ist jetzt fällig.",
|
||||||
|
"lessonReviewHeadlineScheduled": "Diese Lektion ist für die nächste Review-Welle vorgemerkt.",
|
||||||
|
"lessonReviewHintDone": "Die 1/3/7-Tage-Wiederholung ist abgeschlossen. Du kannst die Lektion jetzt flexibel weitertrainieren.",
|
||||||
|
"lessonReviewHintNextDue": "Nächste Fälligkeit: {due}.",
|
||||||
|
"reviewTimeNow": "jetzt",
|
||||||
|
"reviewTimeTomorrow": "morgen",
|
||||||
|
"reviewTimeInDays": "in {count} Tagen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,6 +381,10 @@
|
|||||||
"sale": {
|
"sale": {
|
||||||
"runningGuards": "Guards"
|
"runningGuards": "Guards"
|
||||||
},
|
},
|
||||||
|
"storage": {
|
||||||
|
"buyPartialError": "Error while buying part of the storage capacity.",
|
||||||
|
"sellError": "Error while selling storage capacity."
|
||||||
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"title": "Production",
|
"title": "Production",
|
||||||
"info": "Details about production in the branch.",
|
"info": "Details about production in the branch.",
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"friends": {
|
"friends": {
|
||||||
|
"kicker": "Community",
|
||||||
|
"intro": "Friendships, open requests, and ongoing contacts in one place.",
|
||||||
"title": "Friends",
|
"title": "Friends",
|
||||||
|
"stats": {
|
||||||
|
"existing": "Existing",
|
||||||
|
"open": "Open"
|
||||||
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"existing": "Existing",
|
"existing": "Existing",
|
||||||
"rejected": "Rejected",
|
"rejected": "Rejected",
|
||||||
|
|||||||
@@ -41,6 +41,59 @@
|
|||||||
"message": {
|
"message": {
|
||||||
"close": "Close"
|
"close": "Close"
|
||||||
},
|
},
|
||||||
|
"appShell": {
|
||||||
|
"header": {
|
||||||
|
"tagline": "Community platform",
|
||||||
|
"beta": "Beta",
|
||||||
|
"backend": "Backend",
|
||||||
|
"daemon": "Daemon",
|
||||||
|
"language": "Language"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"systemLabel": "System",
|
||||||
|
"noOpenDialogs": "No open dialogs",
|
||||||
|
"activeWindows": "{count} windows active",
|
||||||
|
"systemReady": "System ready",
|
||||||
|
"systemStatusUnavailable": "System status is not directly available in this view right now."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"widgets": {
|
||||||
|
"dashboard": {
|
||||||
|
"dragHandle": "Move",
|
||||||
|
"loading": "Loading..."
|
||||||
|
},
|
||||||
|
"birthdays": {
|
||||||
|
"today": "Today!",
|
||||||
|
"tomorrow": "Tomorrow",
|
||||||
|
"turningAge": "(turning {age})",
|
||||||
|
"inDays": "{count} days",
|
||||||
|
"empty": "No visible birthdays from friends"
|
||||||
|
},
|
||||||
|
"upcoming": {
|
||||||
|
"today": "Today",
|
||||||
|
"tomorrow": "Tomorrow",
|
||||||
|
"timeAt": "{time}",
|
||||||
|
"allDay": "All day",
|
||||||
|
"empty": "No upcoming appointments"
|
||||||
|
},
|
||||||
|
"appointments": {
|
||||||
|
"title": "📅 Appointments",
|
||||||
|
"loading": "Loading appointments...",
|
||||||
|
"empty": "No upcoming appointments",
|
||||||
|
"loadError": "Appointments could not be loaded"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"noEntries": "No entries",
|
||||||
|
"entriesCount": "({count} entries)",
|
||||||
|
"fieldsCount": "({count} fields)"
|
||||||
|
},
|
||||||
|
"news": {
|
||||||
|
"emptyValue": "—"
|
||||||
|
},
|
||||||
|
"falukant": {
|
||||||
|
"emptyValue": "—"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"male": "Male",
|
"male": "Male",
|
||||||
"female": "Female",
|
"female": "Female",
|
||||||
|
|||||||
@@ -1,5 +1,54 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
|
"dashboard": {
|
||||||
|
"kicker": "Your area",
|
||||||
|
"title": "Welcome back!",
|
||||||
|
"subtitle": "Your personal entry point to community, appointments, Falukant, and ongoing activity.",
|
||||||
|
"edit": "Edit dashboard",
|
||||||
|
"addWidget": "+ Add widget ...",
|
||||||
|
"addAgain": "Add again",
|
||||||
|
"done": "Done",
|
||||||
|
"sectionTitle": "Your overview",
|
||||||
|
"sectionIntro": "Widgets can be moved and adjusted in edit mode.",
|
||||||
|
"widgetTitlePlaceholder": "Title",
|
||||||
|
"removeWidget": "Remove widget",
|
||||||
|
"remove": "Remove",
|
||||||
|
"empty": "No widgets yet. Click “Edit dashboard” and then “+ Add widget”.",
|
||||||
|
"defaultAppointmentsWidget": "Appointments",
|
||||||
|
"loadError": "Dashboard could not be loaded.",
|
||||||
|
"saveError": "Dashboard could not be saved.",
|
||||||
|
"widgetLabels": {
|
||||||
|
"appointments": "Appointments",
|
||||||
|
"falukant": "Falukant",
|
||||||
|
"news": "News",
|
||||||
|
"birthdays": "Birthdays",
|
||||||
|
"upcoming": "Upcoming appointments",
|
||||||
|
"calendar": "Calendar"
|
||||||
|
},
|
||||||
|
"overview": {
|
||||||
|
"activeWidgetsLabel": "Active widgets",
|
||||||
|
"activeWidgetsText": "Your dashboard is modular and can be rearranged at any time.",
|
||||||
|
"availableModulesLabel": "Available modules",
|
||||||
|
"availableModulesText": "You can combine community, calendar, news, and Falukant modules.",
|
||||||
|
"editModeLabel": "Edit mode",
|
||||||
|
"editModeActive": "Active",
|
||||||
|
"editModeInactive": "Off",
|
||||||
|
"editModeActiveText": "Widgets can currently be added and adjusted.",
|
||||||
|
"editModeInactiveText": "Content stays focused and calm to read."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vocabLanding": {
|
||||||
|
"eyebrow": "Learn languages online",
|
||||||
|
"title": "The vocabulary trainer on YourPart combines learning, courses, and practice in one platform.",
|
||||||
|
"lead": "Work with interactive lessons, grow your vocabulary, and use structured content for a motivating learning flow right in the browser.",
|
||||||
|
"cta": "Start for free",
|
||||||
|
"feature1Title": "Interactive courses",
|
||||||
|
"feature1Text": "Courses, lessons, and exercises help you build new language skills step by step.",
|
||||||
|
"feature2Title": "Practice-focused",
|
||||||
|
"feature2Text": "Vocabulary, grammar, and review are aligned with an everyday learning routine.",
|
||||||
|
"feature3Title": "Part of a community",
|
||||||
|
"feature3Text": "The language area is embedded in a larger community platform with blogs, forum, and chat."
|
||||||
|
},
|
||||||
"betaNoticeLabel": "Beta notice:",
|
"betaNoticeLabel": "Beta notice:",
|
||||||
"betaNoticeText": "YourPart is under active development. Features may be incomplete, information may still be missing and things can change.",
|
"betaNoticeText": "YourPart is under active development. Features may be incomplete, information may still be missing and things can change.",
|
||||||
"nologin": {
|
"nologin": {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"personal": {
|
"personal": {
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"title": "Calendar",
|
"title": "Calendar",
|
||||||
|
"kicker": "Planning",
|
||||||
|
"intro": "Appointments, birthdays, and your own entries in one structured overview.",
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
"newEntry": "New Entry",
|
"newEntry": "New Entry",
|
||||||
"editEntry": "Edit Entry",
|
"editEntry": "Edit Entry",
|
||||||
|
|||||||
@@ -57,7 +57,8 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"de": "German",
|
"de": "German",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"ceb": "Bisaya"
|
"ceb": "Bisaya",
|
||||||
|
"es": "Spanish"
|
||||||
},
|
},
|
||||||
"eyecolor": {
|
"eyecolor": {
|
||||||
"blue": "Blue",
|
"blue": "Blue",
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
"socialnetwork": {
|
"socialnetwork": {
|
||||||
"usersearch": {
|
"usersearch": {
|
||||||
|
"kicker": "Community search",
|
||||||
|
"intro": "Find suitable contacts in the community by name, age, and gender.",
|
||||||
|
"ageSeparator": "to",
|
||||||
|
"resultsCount": "{count} results",
|
||||||
|
"openProfile": "Open profile",
|
||||||
"title": "User Search",
|
"title": "User Search",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"age_from": "Age from",
|
"age_from": "Age from",
|
||||||
@@ -120,7 +125,8 @@
|
|||||||
"hideInput": "Hide new entry",
|
"hideInput": "Hide new entry",
|
||||||
"imageUpload": "Image",
|
"imageUpload": "Image",
|
||||||
"submit": "Submit entry",
|
"submit": "Submit entry",
|
||||||
"noEntries": "No entries found"
|
"noEntries": "No entries found",
|
||||||
|
"entryImageAlt": "Guestbook entry image"
|
||||||
},
|
},
|
||||||
"interestedInGender": "Interested in",
|
"interestedInGender": "Interested in",
|
||||||
"hasChildren": "Has children",
|
"hasChildren": "Has children",
|
||||||
@@ -147,6 +153,8 @@
|
|||||||
"weight": "Weight"
|
"weight": "Weight"
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
|
"kicker": "Images and folders",
|
||||||
|
"intro": "Organize your own content, control visibility, and structure it in folders.",
|
||||||
"title": "Gallery",
|
"title": "Gallery",
|
||||||
"folders": "Folders",
|
"folders": "Folders",
|
||||||
"create_folder": "Create folder",
|
"create_folder": "Create folder",
|
||||||
@@ -189,15 +197,22 @@
|
|||||||
},
|
},
|
||||||
"show_image_dialog": {
|
"show_image_dialog": {
|
||||||
"title": "Image"
|
"title": "Image"
|
||||||
}
|
},
|
||||||
|
"imagePreviewAlt": "Image preview",
|
||||||
|
"imageLoadingAlt": "Loading image"
|
||||||
},
|
},
|
||||||
"guestbook": {
|
"guestbook": {
|
||||||
|
"kicker": "Guestbook",
|
||||||
|
"intro": "Messages, feedback, and small glimpses from your network.",
|
||||||
"title": "Guestbook",
|
"title": "Guestbook",
|
||||||
"prevPage": "Back",
|
"prevPage": "Back",
|
||||||
"nextPage": "Next",
|
"nextPage": "Next",
|
||||||
"page": "Page"
|
"page": "Page"
|
||||||
},
|
},
|
||||||
"diary": {
|
"diary": {
|
||||||
|
"kicker": "Personal entries",
|
||||||
|
"intro": "Thoughts, notes, and short updates in a calm personal view.",
|
||||||
|
"placeholder": "Write your diary entry...",
|
||||||
"title": "Diary",
|
"title": "Diary",
|
||||||
"noEntries": "You haven't made any diary entries yet.",
|
"noEntries": "You haven't made any diary entries yet.",
|
||||||
"newEntry": "New diary entry",
|
"newEntry": "New diary entry",
|
||||||
@@ -213,6 +228,16 @@
|
|||||||
"page": "Page"
|
"page": "Page"
|
||||||
},
|
},
|
||||||
"forum": {
|
"forum": {
|
||||||
|
"kicker": "Community forum",
|
||||||
|
"intro": "Topics, discussions, and new posts in one structured place.",
|
||||||
|
"createTitle": "Write a new topic",
|
||||||
|
"createIntro": "Set the title first, then write the post and publish it directly.",
|
||||||
|
"cancelCreation": "Cancel",
|
||||||
|
"creationHint": "Title and content must both be filled in.",
|
||||||
|
"communityFallback": "Community",
|
||||||
|
"topicIntro": "Discussions, replies, and new posts in a focused reading view.",
|
||||||
|
"topicCreated": "Topic created successfully.",
|
||||||
|
"topicCreateError": "Error creating topic",
|
||||||
"title": "Forum",
|
"title": "Forum",
|
||||||
"showNewTopic": "Create new topic",
|
"showNewTopic": "Create new topic",
|
||||||
"hideNewTopic": "Cancel creation",
|
"hideNewTopic": "Cancel creation",
|
||||||
@@ -277,9 +302,32 @@
|
|||||||
"videoUploadHint": "Upload videos for your unlocked erotic area here and maintain title and description directly during upload.",
|
"videoUploadHint": "Upload videos for your unlocked erotic area here and maintain title and description directly during upload.",
|
||||||
"videoDescription": "Description",
|
"videoDescription": "Description",
|
||||||
"videoFile": "Video file",
|
"videoFile": "Video file",
|
||||||
|
"videoFormats": "MP4, WEBM, OGG, MOV",
|
||||||
"myVideos": "My videos",
|
"myVideos": "My videos",
|
||||||
|
"sharedVideos": "Shared videos",
|
||||||
|
"foreignVideosIntro": "Shared videos from the adult area.",
|
||||||
|
"foreignVideosOnlyHint": "You only see videos here that were shared with you for the adult area.",
|
||||||
|
"sharedVideosIntro": "Visible videos from shared adult areas.",
|
||||||
|
"noSharedVideos": "There are currently no shared videos available for you.",
|
||||||
|
"libraryTitle": "Library",
|
||||||
|
"libraryIntro": "Your uploads, visibility settings, and reports in one place.",
|
||||||
|
"libraryEmptyHint": "Create your first video on the left and manage it here afterwards in the library.",
|
||||||
|
"latestUpload": "Latest upload",
|
||||||
|
"visibleVideos": "Visible videos",
|
||||||
|
"moderationCases": "Moderation cases",
|
||||||
|
"notesTitle": "Notes",
|
||||||
|
"friendsVisibilityHint": "Friends can only see content if they are adults and unlocked for the adult area.",
|
||||||
|
"selectedUsersVisibilityHint": "Specifically shared people must also be adults and unlocked.",
|
||||||
|
"selectedUsersPlaceholder": "anna, bert, clara",
|
||||||
|
"imagePreviewAlt": "Image preview",
|
||||||
|
"imageLoadingAlt": "Loading image",
|
||||||
|
"untitled": "Untitled",
|
||||||
|
"noUploadYet": "No upload yet",
|
||||||
|
"closeEditing": "Close editing",
|
||||||
|
"editVisibility": "Edit visibility",
|
||||||
"noVideos": "You have not uploaded any erotic videos yet.",
|
"noVideos": "You have not uploaded any erotic videos yet.",
|
||||||
"reportAction": "Report",
|
"reportAction": "Report",
|
||||||
|
"reportHint": "Use {action} directly on the respective item when content should be reviewed.",
|
||||||
"reportNote": "Short note for moderation",
|
"reportNote": "Short note for moderation",
|
||||||
"submitReport": "Submit report",
|
"submitReport": "Submit report",
|
||||||
"reportSubmitted": "The report has been submitted.",
|
"reportSubmitted": "The report has been submitted.",
|
||||||
@@ -323,6 +371,36 @@
|
|||||||
"vocab": {
|
"vocab": {
|
||||||
"title": "Vocabulary trainer",
|
"title": "Vocabulary trainer",
|
||||||
"description": "Create languages (or subscribe to them) and share them with friends.",
|
"description": "Create languages (or subscribe to them) and share them with friends.",
|
||||||
|
"heroEyebrow": "Language learning",
|
||||||
|
"summaryTotalLabel": "Languages total",
|
||||||
|
"summaryTotalIntro": "All active language areas where you use or manage content.",
|
||||||
|
"summaryOwnedLabel": "Owned areas",
|
||||||
|
"summaryOwnedIntro": "This is where you actively create content, chapters, and learning material yourself.",
|
||||||
|
"summarySubscribedLabel": "Subscribed",
|
||||||
|
"summarySubscribedIntro": "These areas are more about learning and progress than administration.",
|
||||||
|
"taskCreateEyebrow": "Quick start",
|
||||||
|
"taskCreateTitle": "Create new language",
|
||||||
|
"taskCreateIntro": "The best starting point if you want to structure and maintain content yourself.",
|
||||||
|
"taskContinueEyebrow": "Continue learning",
|
||||||
|
"taskContinueTitle": "Open courses and chapters",
|
||||||
|
"taskContinueIntro": "Jump straight into existing learning paths and continue with available courses.",
|
||||||
|
"ownedSectionTitle": "Owned languages",
|
||||||
|
"ownedSectionIntro": "Direct access to editing, chapters, and course management.",
|
||||||
|
"ownedHint": "Manage and maintain content",
|
||||||
|
"ownedEmpty": "No owned language areas yet.",
|
||||||
|
"subscribedSectionTitle": "Subscribed languages",
|
||||||
|
"subscribedSectionIntro": "Good for quickly getting back into learning without admin overhead.",
|
||||||
|
"subscribedHint": "Learn, practice, and review progress",
|
||||||
|
"subscribedEmpty": "No subscribed languages available.",
|
||||||
|
"languageHeroEyebrow": "Language",
|
||||||
|
"languageHeroIntro": "Chapters, search tools, and sharing for this language in one place.",
|
||||||
|
"newLanguageHeroEyebrow": "Vocabulary trainer",
|
||||||
|
"newLanguageHeroIntro": "Create a new language, generate a share code, and jump straight into editing.",
|
||||||
|
"newLanguageNameHint": "A short, clear language name is enough to get started.",
|
||||||
|
"newLanguageNameValidation": "The name should have at least 2 characters.",
|
||||||
|
"subscribeHeroEyebrow": "Vocabulary trainer",
|
||||||
|
"chapterHeroEyebrow": "Vocabulary trainer",
|
||||||
|
"chapterHeroIntro": "Browse chapter content, maintain vocabulary, and jump directly into practice.",
|
||||||
"newLanguage": "New language",
|
"newLanguage": "New language",
|
||||||
"newLanguageTitle": "Create new language",
|
"newLanguageTitle": "Create new language",
|
||||||
"languageName": "Language name",
|
"languageName": "Language name",
|
||||||
@@ -570,7 +648,91 @@
|
|||||||
"languageAssistantPatternHint": "Focus especially on this pattern",
|
"languageAssistantPatternHint": "Focus especially on this pattern",
|
||||||
"languageAssistantPresetPracticeStart": "Let's practice a short everyday dialogue for the lesson \"{lesson}\". Please ask me questions and wait for my answers.",
|
"languageAssistantPresetPracticeStart": "Let's practice a short everyday dialogue for the lesson \"{lesson}\". Please ask me questions and wait for my answers.",
|
||||||
"languageAssistantPresetCorrectStart": "I want to write my own sentences for the lesson \"{lesson}\". Please correct my answers briefly and clearly.",
|
"languageAssistantPresetCorrectStart": "I want to write my own sentences for the lesson \"{lesson}\". Please correct my answers briefly and clearly.",
|
||||||
"thisLesson": "this lesson"
|
"thisLesson": "this lesson",
|
||||||
|
"courseKicker": "Learning course",
|
||||||
|
"courseListKicker": "Courses",
|
||||||
|
"courseListIntro": "Filter public and your own learning courses, find the right one, and continue directly.",
|
||||||
|
"courseShareCodePlaceholder": "e.g. abc123def456",
|
||||||
|
"courseFlowEyebrow": "Daily flow",
|
||||||
|
"courseFlowTitle": "Best next step for today",
|
||||||
|
"courseFlowIntro": "The order follows the concept: due reviews first, then the current block, then intensive review, and finally free practice.",
|
||||||
|
"courseFlowReviewStat": "Due review: {count}",
|
||||||
|
"courseFlowBlockStat": "Active block: {block}",
|
||||||
|
"courseFlowReviewTitle": "Due review",
|
||||||
|
"courseFlowReviewDescription": "Lessons already completed that should come back today.",
|
||||||
|
"courseFlowReviewEmpty": "No older lesson is marked as due for review today.",
|
||||||
|
"courseFlowBlockTitle": "Current block",
|
||||||
|
"courseFlowBlockDescription": "This is where the next regular progress step in the course lives.",
|
||||||
|
"courseFlowBlockEmpty": "The current block is already done or there is no open block lesson right now.",
|
||||||
|
"courseFlowIntensiveTitle": "Due intensive review",
|
||||||
|
"courseFlowIntensiveDescription": "Condensed review once the block before it is mostly stable.",
|
||||||
|
"courseFlowIntensiveEmpty": "No new intensive review is unlocked right now.",
|
||||||
|
"courseFlowPracticeTitle": "Free practice",
|
||||||
|
"courseFlowPracticeDescription": "Completed lessons for relaxed extra practice outside the required path.",
|
||||||
|
"courseFlowPracticeEmpty": "As soon as you complete your first lessons, they will appear here for free practice.",
|
||||||
|
"practiceInTrainer": "Practice in trainer",
|
||||||
|
"lessonsCount": "{count} lessons",
|
||||||
|
"lessonBlockLabel": "Block {number}",
|
||||||
|
"lessonIntensiveBadge": "Intensive review",
|
||||||
|
"addLessonValidation": "Please provide number, title, and chapter.",
|
||||||
|
"addLessonSuccess": "Lesson created successfully.",
|
||||||
|
"addLessonError": "Could not add the lesson.",
|
||||||
|
"createCourseError": "Could not create the course.",
|
||||||
|
"deleteLessonTitle": "Delete lesson",
|
||||||
|
"deleteLessonSuccess": "Lesson deleted successfully.",
|
||||||
|
"deleteLessonError": "Could not delete the lesson.",
|
||||||
|
"enrollCourseError": "Could not enroll in the course.",
|
||||||
|
"editLessonPending": "Editing individual lessons is still pending.",
|
||||||
|
"timeToday": "today",
|
||||||
|
"timeSinceOneDay": "since 1 day",
|
||||||
|
"timeSinceDays": "since {count} days",
|
||||||
|
"reviewDueNow": "due now",
|
||||||
|
"reviewDueTomorrow": "due tomorrow",
|
||||||
|
"reviewDueInDays": "due in {count} days",
|
||||||
|
"reviewDueToday": "due today",
|
||||||
|
"reviewDueSinceOneDay": "due since 1 day",
|
||||||
|
"reviewDueSinceDays": "due since {count} days",
|
||||||
|
"reviewStageDay1": "Day 1",
|
||||||
|
"reviewStageDay3": "Day 3",
|
||||||
|
"reviewStageDay7": "Day 7",
|
||||||
|
"reviewStageCompleted": "Review completed",
|
||||||
|
"phaseQuickstart": "Quick start",
|
||||||
|
"phaseDailyLife": "Daily life",
|
||||||
|
"phaseStabilization": "Stabilization",
|
||||||
|
"phaseDefault": "Learning phase",
|
||||||
|
"didacticModeCoreInput": "New content",
|
||||||
|
"didacticModeGuidedDialogue": "Guided dialogue",
|
||||||
|
"didacticModeContrastTraining": "Contrast training",
|
||||||
|
"didacticModePatternDrill": "Pattern drill",
|
||||||
|
"didacticModeRealLifeScenario": "Real-life scenario",
|
||||||
|
"didacticModeIntensiveReview": "Review phase",
|
||||||
|
"didacticModeCheckpoint": "Checkpoint",
|
||||||
|
"didacticModeDefault": "Learning unit",
|
||||||
|
"didacticModeFocusDefault": "Learning focus",
|
||||||
|
"lessonMetaFocus": "Focus",
|
||||||
|
"lessonMetaPhase": "Phase",
|
||||||
|
"lessonMetaNewUnits": "New units",
|
||||||
|
"lessonMetaReview": "Review",
|
||||||
|
"intensiveReviewTitle": "Intensive review phase",
|
||||||
|
"intensiveReviewIntro": "This lesson prioritizes review and consolidation. New material is intentionally reduced so existing patterns can stabilize.",
|
||||||
|
"reviewPriorityTitle": "Review is mixed in step by step",
|
||||||
|
"reviewPriorityIntro": "The focus starts on the new terms of this lesson. As you progress, older vocabulary is gradually mixed in.",
|
||||||
|
"exerciseLockTitle": "Chapter test still locked",
|
||||||
|
"trainerStartWithReview": "Start with the new vocabulary from this lesson. As you practice, the trainer will automatically mix in fitting review items.",
|
||||||
|
"startLesson": "Start lesson",
|
||||||
|
"trainerProgressNewContent": "New content: {current}/{target}",
|
||||||
|
"trainerProgressReview": "Review: {count}",
|
||||||
|
"trainerProgressMixShare": "Mixed share: {percent}%",
|
||||||
|
"unknownExerciseTypeNotice": "This exercise type is not displayed interactively in the current view yet.",
|
||||||
|
"unknownExerciseTypeLabel": "Type: {type}",
|
||||||
|
"lessonReviewHeadlineDone": "This lesson has reached the free practice stage.",
|
||||||
|
"lessonReviewHeadlineDue": "This review wave is due now.",
|
||||||
|
"lessonReviewHeadlineScheduled": "This lesson is scheduled for the next review wave.",
|
||||||
|
"lessonReviewHintDone": "The 1/3/7-day review cycle is complete. You can now continue practicing this lesson freely.",
|
||||||
|
"lessonReviewHintNextDue": "Next due date: {due}.",
|
||||||
|
"reviewTimeNow": "now",
|
||||||
|
"reviewTimeTomorrow": "tomorrow",
|
||||||
|
"reviewTimeInDays": "in {count} days"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -430,7 +430,9 @@
|
|||||||
"selectStockType": "Seleccionar tipo de almacén",
|
"selectStockType": "Seleccionar tipo de almacén",
|
||||||
"costPerUnit": "Coste por unidad",
|
"costPerUnit": "Coste por unidad",
|
||||||
"buycost": "Coste",
|
"buycost": "Coste",
|
||||||
"sellincome": "Ingresos"
|
"sellincome": "Ingresos",
|
||||||
|
"buyPartialError": "Error al comprar parte de la capacidad del almacén.",
|
||||||
|
"sellError": "Error al vender la capacidad del almacén."
|
||||||
},
|
},
|
||||||
"vehicles": {
|
"vehicles": {
|
||||||
"cargo_cart": "Carro de carga",
|
"cargo_cart": "Carro de carga",
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"friends": {
|
"friends": {
|
||||||
|
"kicker": "Comunidad",
|
||||||
|
"intro": "Amistades, solicitudes abiertas y contactos en curso en un solo lugar.",
|
||||||
"title": "Amigos",
|
"title": "Amigos",
|
||||||
|
"stats": {
|
||||||
|
"existing": "Activos",
|
||||||
|
"open": "Abiertos"
|
||||||
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"existing": "Actuales",
|
"existing": "Actuales",
|
||||||
"rejected": "Rechazadas",
|
"rejected": "Rechazadas",
|
||||||
|
|||||||
@@ -41,6 +41,59 @@
|
|||||||
"message": {
|
"message": {
|
||||||
"close": "Cerrar"
|
"close": "Cerrar"
|
||||||
},
|
},
|
||||||
|
"appShell": {
|
||||||
|
"header": {
|
||||||
|
"tagline": "Plataforma comunitaria",
|
||||||
|
"beta": "Beta",
|
||||||
|
"backend": "Backend",
|
||||||
|
"daemon": "Daemon",
|
||||||
|
"language": "Idioma"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"systemLabel": "Sistema",
|
||||||
|
"noOpenDialogs": "No hay diálogos abiertos",
|
||||||
|
"activeWindows": "{count} ventanas activas",
|
||||||
|
"systemReady": "Sistema listo",
|
||||||
|
"systemStatusUnavailable": "El estado del sistema no está disponible directamente en esta vista en este momento."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"widgets": {
|
||||||
|
"dashboard": {
|
||||||
|
"dragHandle": "Mover",
|
||||||
|
"loading": "Cargando..."
|
||||||
|
},
|
||||||
|
"birthdays": {
|
||||||
|
"today": "¡Hoy!",
|
||||||
|
"tomorrow": "Mañana",
|
||||||
|
"turningAge": "(cumple {age})",
|
||||||
|
"inDays": "{count} días",
|
||||||
|
"empty": "No hay cumpleaños visibles de amigos"
|
||||||
|
},
|
||||||
|
"upcoming": {
|
||||||
|
"today": "Hoy",
|
||||||
|
"tomorrow": "Mañana",
|
||||||
|
"timeAt": "{time} h",
|
||||||
|
"allDay": "Todo el día",
|
||||||
|
"empty": "No hay citas próximas"
|
||||||
|
},
|
||||||
|
"appointments": {
|
||||||
|
"title": "📅 Citas",
|
||||||
|
"loading": "Cargando citas...",
|
||||||
|
"empty": "No hay citas próximas",
|
||||||
|
"loadError": "No se pudieron cargar las citas"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"noEntries": "No hay entradas",
|
||||||
|
"entriesCount": "({count} entradas)",
|
||||||
|
"fieldsCount": "({count} campos)"
|
||||||
|
},
|
||||||
|
"news": {
|
||||||
|
"emptyValue": "—"
|
||||||
|
},
|
||||||
|
"falukant": {
|
||||||
|
"emptyValue": "—"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"male": "Masculino",
|
"male": "Masculino",
|
||||||
"female": "Femenino",
|
"female": "Femenino",
|
||||||
|
|||||||
@@ -1,5 +1,54 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
|
"dashboard": {
|
||||||
|
"kicker": "Tu área",
|
||||||
|
"title": "¡Bienvenido de nuevo!",
|
||||||
|
"subtitle": "Tu punto de entrada personal a la comunidad, las citas, Falukant y la actividad en curso.",
|
||||||
|
"edit": "Editar panel",
|
||||||
|
"addWidget": "+ Añadir widget ...",
|
||||||
|
"addAgain": "Añadir de nuevo",
|
||||||
|
"done": "Listo",
|
||||||
|
"sectionTitle": "Tu resumen",
|
||||||
|
"sectionIntro": "Los widgets se pueden mover y ajustar en el modo de edición.",
|
||||||
|
"widgetTitlePlaceholder": "Título",
|
||||||
|
"removeWidget": "Eliminar widget",
|
||||||
|
"remove": "Eliminar",
|
||||||
|
"empty": "Aún no hay widgets. Haz clic en “Editar panel” y luego en “+ Añadir widget”.",
|
||||||
|
"defaultAppointmentsWidget": "Citas",
|
||||||
|
"loadError": "No se pudo cargar el panel.",
|
||||||
|
"saveError": "No se pudo guardar el panel.",
|
||||||
|
"widgetLabels": {
|
||||||
|
"appointments": "Citas",
|
||||||
|
"falukant": "Falukant",
|
||||||
|
"news": "Noticias",
|
||||||
|
"birthdays": "Cumpleaños",
|
||||||
|
"upcoming": "Próximas citas",
|
||||||
|
"calendar": "Calendario"
|
||||||
|
},
|
||||||
|
"overview": {
|
||||||
|
"activeWidgetsLabel": "Widgets activos",
|
||||||
|
"activeWidgetsText": "Tu panel es modular y se puede reorganizar en cualquier momento.",
|
||||||
|
"availableModulesLabel": "Módulos disponibles",
|
||||||
|
"availableModulesText": "Puedes combinar módulos de comunidad, calendario, noticias y Falukant.",
|
||||||
|
"editModeLabel": "Modo de edición",
|
||||||
|
"editModeActive": "Activo",
|
||||||
|
"editModeInactive": "Desactivado",
|
||||||
|
"editModeActiveText": "Ahora mismo se pueden añadir y ajustar widgets.",
|
||||||
|
"editModeInactiveText": "El contenido sigue siendo claro y fácil de leer."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vocabLanding": {
|
||||||
|
"eyebrow": "Aprende idiomas en línea",
|
||||||
|
"title": "El entrenador de vocabulario en YourPart combina aprendizaje, cursos y ejercicios en una sola plataforma.",
|
||||||
|
"lead": "Trabaja con lecciones interactivas, amplía tu vocabulario y usa contenido estructurado para un flujo de aprendizaje motivador directamente en el navegador.",
|
||||||
|
"cta": "Empezar gratis",
|
||||||
|
"feature1Title": "Cursos interactivos",
|
||||||
|
"feature1Text": "Cursos, lecciones y ejercicios ayudan a construir nuevas competencias lingüísticas de forma sistemática.",
|
||||||
|
"feature2Title": "Orientado a la práctica",
|
||||||
|
"feature2Text": "Vocabulario, gramática y repaso se adaptan a una rutina de aprendizaje cotidiana.",
|
||||||
|
"feature3Title": "Parte de una comunidad",
|
||||||
|
"feature3Text": "El área de idiomas está integrada en una plataforma comunitaria más amplia con blogs, foro y chat."
|
||||||
|
},
|
||||||
"betaNoticeLabel": "Aviso beta:",
|
"betaNoticeLabel": "Aviso beta:",
|
||||||
"betaNoticeText": "YourPart está en desarrollo activo. Algunas funciones pueden estar incompletas, pueden faltar contenidos y puede haber cambios.",
|
"betaNoticeText": "YourPart está en desarrollo activo. Algunas funciones pueden estar incompletas, pueden faltar contenidos y puede haber cambios.",
|
||||||
"nologin": {
|
"nologin": {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"personal": {
|
"personal": {
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"title": "Calendario",
|
"title": "Calendario",
|
||||||
|
"kicker": "Planificación",
|
||||||
|
"intro": "Citas, cumpleaños y tus propias entradas en una vista estructurada.",
|
||||||
"today": "Hoy",
|
"today": "Hoy",
|
||||||
"newEntry": "Nueva entrada",
|
"newEntry": "Nueva entrada",
|
||||||
"editEntry": "Editar entrada",
|
"editEntry": "Editar entrada",
|
||||||
|
|||||||
@@ -57,7 +57,8 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"de": "Alemán",
|
"de": "Alemán",
|
||||||
"en": "Inglés",
|
"en": "Inglés",
|
||||||
"ceb": "Bisaya"
|
"ceb": "Bisaya",
|
||||||
|
"es": "Español"
|
||||||
},
|
},
|
||||||
"eyecolor": {
|
"eyecolor": {
|
||||||
"blue": "Azul",
|
"blue": "Azul",
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
"socialnetwork": {
|
"socialnetwork": {
|
||||||
"usersearch": {
|
"usersearch": {
|
||||||
|
"kicker": "Búsqueda en la comunidad",
|
||||||
|
"intro": "Encuentra contactos adecuados en la comunidad por nombre, edad y género.",
|
||||||
|
"ageSeparator": "hasta",
|
||||||
|
"resultsCount": "{count} resultados",
|
||||||
|
"openProfile": "Abrir perfil",
|
||||||
"title": "Búsqueda de usuarios",
|
"title": "Búsqueda de usuarios",
|
||||||
"username": "Nombre de usuario",
|
"username": "Nombre de usuario",
|
||||||
"age_from": "Edad desde",
|
"age_from": "Edad desde",
|
||||||
@@ -120,7 +125,8 @@
|
|||||||
"hideInput": "Ocultar nueva entrada",
|
"hideInput": "Ocultar nueva entrada",
|
||||||
"imageUpload": "Imagen",
|
"imageUpload": "Imagen",
|
||||||
"submit": "Enviar entrada",
|
"submit": "Enviar entrada",
|
||||||
"noEntries": "No se han encontrado entradas"
|
"noEntries": "No se han encontrado entradas",
|
||||||
|
"entryImageAlt": "Imagen de la entrada del libro de visitas"
|
||||||
},
|
},
|
||||||
"interestedInGender": "Interesado/a en",
|
"interestedInGender": "Interesado/a en",
|
||||||
"hasChildren": "Tiene hijos",
|
"hasChildren": "Tiene hijos",
|
||||||
@@ -147,6 +153,8 @@
|
|||||||
"weight": "Peso"
|
"weight": "Peso"
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
|
"kicker": "Imágenes y carpetas",
|
||||||
|
"intro": "Organiza tu propio contenido, controla su visibilidad y ordénalo en carpetas.",
|
||||||
"title": "Galería",
|
"title": "Galería",
|
||||||
"folders": "Carpetas",
|
"folders": "Carpetas",
|
||||||
"create_folder": "Crear carpeta",
|
"create_folder": "Crear carpeta",
|
||||||
@@ -189,15 +197,22 @@
|
|||||||
},
|
},
|
||||||
"show_image_dialog": {
|
"show_image_dialog": {
|
||||||
"title": "Imagen"
|
"title": "Imagen"
|
||||||
}
|
},
|
||||||
|
"imagePreviewAlt": "Vista previa de la imagen",
|
||||||
|
"imageLoadingAlt": "Cargando imagen"
|
||||||
},
|
},
|
||||||
"guestbook": {
|
"guestbook": {
|
||||||
|
"kicker": "Libro de visitas",
|
||||||
|
"intro": "Mensajes, comentarios y pequeños vistazos desde tu red.",
|
||||||
"title": "Libro de visitas",
|
"title": "Libro de visitas",
|
||||||
"prevPage": "Atrás",
|
"prevPage": "Atrás",
|
||||||
"nextPage": "Siguiente",
|
"nextPage": "Siguiente",
|
||||||
"page": "Página"
|
"page": "Página"
|
||||||
},
|
},
|
||||||
"diary": {
|
"diary": {
|
||||||
|
"kicker": "Entradas personales",
|
||||||
|
"intro": "Pensamientos, notas y pequeñas actualizaciones en una vista tranquila y personal.",
|
||||||
|
"placeholder": "Escribe tu entrada del diario...",
|
||||||
"title": "Diario",
|
"title": "Diario",
|
||||||
"noEntries": "Aún no has escrito ninguna entrada en el diario.",
|
"noEntries": "Aún no has escrito ninguna entrada en el diario.",
|
||||||
"newEntry": "Nueva entrada",
|
"newEntry": "Nueva entrada",
|
||||||
@@ -213,6 +228,16 @@
|
|||||||
"page": "Página"
|
"page": "Página"
|
||||||
},
|
},
|
||||||
"forum": {
|
"forum": {
|
||||||
|
"kicker": "Foro de la comunidad",
|
||||||
|
"intro": "Temas, debates y nuevas publicaciones en un lugar estructurado.",
|
||||||
|
"createTitle": "Redactar un nuevo tema",
|
||||||
|
"createIntro": "Primero pon el título, luego escribe la publicación y después publícala directamente.",
|
||||||
|
"cancelCreation": "Cancelar",
|
||||||
|
"creationHint": "El título y el contenido deben estar completos.",
|
||||||
|
"communityFallback": "Comunidad",
|
||||||
|
"topicIntro": "Debates, respuestas y nuevas publicaciones en una vista de lectura enfocada.",
|
||||||
|
"topicCreated": "Tema creado correctamente.",
|
||||||
|
"topicCreateError": "Error al crear el tema",
|
||||||
"title": "Forum",
|
"title": "Forum",
|
||||||
"showNewTopic": "Crear nuevo tema",
|
"showNewTopic": "Crear nuevo tema",
|
||||||
"hideNewTopic": "Cancelar creación",
|
"hideNewTopic": "Cancelar creación",
|
||||||
@@ -277,9 +302,32 @@
|
|||||||
"videoUploadHint": "Sube aquí vídeos para tu área erótica desbloqueada y completa título y descripción directamente durante la subida.",
|
"videoUploadHint": "Sube aquí vídeos para tu área erótica desbloqueada y completa título y descripción directamente durante la subida.",
|
||||||
"videoDescription": "Descripción",
|
"videoDescription": "Descripción",
|
||||||
"videoFile": "Archivo de vídeo",
|
"videoFile": "Archivo de vídeo",
|
||||||
|
"videoFormats": "MP4, WEBM, OGG, MOV",
|
||||||
"myVideos": "Mis vídeos",
|
"myVideos": "Mis vídeos",
|
||||||
|
"sharedVideos": "Vídeos compartidos",
|
||||||
|
"foreignVideosIntro": "Vídeos compartidos del área para adultos.",
|
||||||
|
"foreignVideosOnlyHint": "Aquí solo ves vídeos que han sido compartidos contigo para el área para adultos.",
|
||||||
|
"sharedVideosIntro": "Vídeos visibles de áreas para adultos compartidas.",
|
||||||
|
"noSharedVideos": "Ahora mismo no hay vídeos compartidos disponibles para ti.",
|
||||||
|
"libraryTitle": "Biblioteca",
|
||||||
|
"libraryIntro": "Tus subidas, permisos y reportes en un solo lugar.",
|
||||||
|
"libraryEmptyHint": "Crea a la izquierda tu primer vídeo y luego administraciónalo aquí en la biblioteca.",
|
||||||
|
"latestUpload": "Última subida",
|
||||||
|
"visibleVideos": "Vídeos visibles",
|
||||||
|
"moderationCases": "Casos de moderación",
|
||||||
|
"notesTitle": "Notas",
|
||||||
|
"friendsVisibilityHint": "Los amigos solo ven el contenido si son mayores de edad y están autorizados para el área para adultos.",
|
||||||
|
"selectedUsersVisibilityHint": "Las personas autorizadas de forma específica también deben ser mayores de edad y estar autorizadas.",
|
||||||
|
"selectedUsersPlaceholder": "anna, bert, clara",
|
||||||
|
"imagePreviewAlt": "Vista previa de la imagen",
|
||||||
|
"imageLoadingAlt": "Cargando imagen",
|
||||||
|
"untitled": "Sin título",
|
||||||
|
"noUploadYet": "Aún no hay subida",
|
||||||
|
"closeEditing": "Cerrar edición",
|
||||||
|
"editVisibility": "Editar permisos",
|
||||||
"noVideos": "Todavía no has subido vídeos eróticos.",
|
"noVideos": "Todavía no has subido vídeos eróticos.",
|
||||||
"reportAction": "Denunciar",
|
"reportAction": "Denunciar",
|
||||||
|
"reportHint": "Usa {action} directamente en el elemento correspondiente si el contenido debe revisarse.",
|
||||||
"reportNote": "Nota breve para moderación",
|
"reportNote": "Nota breve para moderación",
|
||||||
"submitReport": "Enviar denuncia",
|
"submitReport": "Enviar denuncia",
|
||||||
"reportSubmitted": "La denuncia fue enviada.",
|
"reportSubmitted": "La denuncia fue enviada.",
|
||||||
@@ -323,6 +371,36 @@
|
|||||||
"vocab": {
|
"vocab": {
|
||||||
"title": "Entrenador de vocabulario",
|
"title": "Entrenador de vocabulario",
|
||||||
"description": "Crea idiomas (o suscríbete) y compártelos con tus amigos.",
|
"description": "Crea idiomas (o suscríbete) y compártelos con tus amigos.",
|
||||||
|
"heroEyebrow": "Aprendizaje de idiomas",
|
||||||
|
"summaryTotalLabel": "Idiomas en total",
|
||||||
|
"summaryTotalIntro": "Todas las áreas de idioma activas donde usas o administras contenido.",
|
||||||
|
"summaryOwnedLabel": "Áreas propias",
|
||||||
|
"summaryOwnedIntro": "Aquí creas por tu cuenta contenido, capítulos y material de aprendizaje.",
|
||||||
|
"summarySubscribedLabel": "Suscrito",
|
||||||
|
"summarySubscribedIntro": "Estas áreas están pensadas más para aprender y avanzar que para administrar.",
|
||||||
|
"taskCreateEyebrow": "Inicio rápido",
|
||||||
|
"taskCreateTitle": "Crear nuevo idioma",
|
||||||
|
"taskCreateIntro": "La mejor entrada si quieres estructurar y mantener el contenido tú mismo.",
|
||||||
|
"taskContinueEyebrow": "Seguir aprendiendo",
|
||||||
|
"taskContinueTitle": "Abrir cursos y capítulos",
|
||||||
|
"taskContinueIntro": "Entra directamente en rutas de aprendizaje ya existentes y sigue con los cursos disponibles.",
|
||||||
|
"ownedSectionTitle": "Idiomas propios",
|
||||||
|
"ownedSectionIntro": "Acceso directo a edición, capítulos y gestión de cursos.",
|
||||||
|
"ownedHint": "Administrar y mantener contenido",
|
||||||
|
"ownedEmpty": "Todavía no hay áreas de idioma propias.",
|
||||||
|
"subscribedSectionTitle": "Idiomas suscritos",
|
||||||
|
"subscribedSectionIntro": "Ideal para volver rápido al aprendizaje sin carga administrativa.",
|
||||||
|
"subscribedHint": "Aprender, practicar y ver el progreso",
|
||||||
|
"subscribedEmpty": "No hay idiomas suscritos disponibles.",
|
||||||
|
"languageHeroEyebrow": "Idioma",
|
||||||
|
"languageHeroIntro": "Capítulos, búsqueda y compartición de este idioma en un solo lugar.",
|
||||||
|
"newLanguageHeroEyebrow": "Entrenador de vocabulario",
|
||||||
|
"newLanguageHeroIntro": "Crea un nuevo idioma, genera un código para compartir y pasa directamente a la edición.",
|
||||||
|
"newLanguageNameHint": "Basta con un nombre corto y claro para empezar.",
|
||||||
|
"newLanguageNameValidation": "El nombre debe tener al menos 2 caracteres.",
|
||||||
|
"subscribeHeroEyebrow": "Entrenador de vocabulario",
|
||||||
|
"chapterHeroEyebrow": "Entrenador de vocabulario",
|
||||||
|
"chapterHeroIntro": "Explora el contenido del capítulo, mantén el vocabulario y pasa directamente a la práctica.",
|
||||||
"newLanguage": "Nuevo idioma",
|
"newLanguage": "Nuevo idioma",
|
||||||
"newLanguageTitle": "Crear nuevo idioma",
|
"newLanguageTitle": "Crear nuevo idioma",
|
||||||
"languageName": "Nombre del idioma",
|
"languageName": "Nombre del idioma",
|
||||||
@@ -568,7 +646,91 @@
|
|||||||
"languageAssistantPatternHint": "Concéntrate especialmente en este patrón",
|
"languageAssistantPatternHint": "Concéntrate especialmente en este patrón",
|
||||||
"languageAssistantPresetPracticeStart": "Practiquemos un diálogo cotidiano corto para la lección \"{lesson}\". Hazme preguntas y espera mis respuestas.",
|
"languageAssistantPresetPracticeStart": "Practiquemos un diálogo cotidiano corto para la lección \"{lesson}\". Hazme preguntas y espera mis respuestas.",
|
||||||
"languageAssistantPresetCorrectStart": "Quiero escribir mis propias frases para la lección \"{lesson}\". Corrige mis respuestas de forma breve y clara.",
|
"languageAssistantPresetCorrectStart": "Quiero escribir mis propias frases para la lección \"{lesson}\". Corrige mis respuestas de forma breve y clara.",
|
||||||
"thisLesson": "esta lección"
|
"thisLesson": "esta lección",
|
||||||
|
"courseKicker": "Curso de aprendizaje",
|
||||||
|
"courseListKicker": "Cursos",
|
||||||
|
"courseListIntro": "Filtra cursos públicos y propios, encuentra el adecuado y continúa directamente.",
|
||||||
|
"courseShareCodePlaceholder": "p. ej. abc123def456",
|
||||||
|
"courseFlowEyebrow": "Flujo del día",
|
||||||
|
"courseFlowTitle": "La mejor continuación para hoy",
|
||||||
|
"courseFlowIntro": "El orden sigue el concepto: primero los repasos pendientes, luego el bloque actual, después la fase intensiva y al final la práctica libre.",
|
||||||
|
"courseFlowReviewStat": "Repaso pendiente: {count}",
|
||||||
|
"courseFlowBlockStat": "Bloque activo: {block}",
|
||||||
|
"courseFlowReviewTitle": "Repaso pendiente",
|
||||||
|
"courseFlowReviewDescription": "Lecciones ya completadas que deberían volver hoy.",
|
||||||
|
"courseFlowReviewEmpty": "Hoy no hay ninguna lección antigua marcada como repaso pendiente.",
|
||||||
|
"courseFlowBlockTitle": "Bloque actual",
|
||||||
|
"courseFlowBlockDescription": "Aquí está el siguiente progreso regular dentro del curso.",
|
||||||
|
"courseFlowBlockEmpty": "El bloque actual ya está terminado o ahora mismo no hay ninguna lección abierta del bloque.",
|
||||||
|
"courseFlowIntensiveTitle": "Fase intensiva pendiente",
|
||||||
|
"courseFlowIntensiveDescription": "Repaso concentrado cuando el bloque anterior ya está bastante asentado.",
|
||||||
|
"courseFlowIntensiveEmpty": "Ahora mismo no hay ninguna nueva fase intensiva desbloqueada.",
|
||||||
|
"courseFlowPracticeTitle": "Práctica libre",
|
||||||
|
"courseFlowPracticeDescription": "Lecciones completadas para repasar con calma fuera del camino obligatorio.",
|
||||||
|
"courseFlowPracticeEmpty": "En cuanto completes tus primeras lecciones, aparecerán aquí para práctica libre.",
|
||||||
|
"practiceInTrainer": "Practicar en el entrenador",
|
||||||
|
"lessonsCount": "{count} lecciones",
|
||||||
|
"lessonBlockLabel": "Bloque {number}",
|
||||||
|
"lessonIntensiveBadge": "Repaso intensivo",
|
||||||
|
"addLessonValidation": "Indica por completo el número, el título y el capítulo.",
|
||||||
|
"addLessonSuccess": "Lección creada correctamente.",
|
||||||
|
"addLessonError": "No se pudo añadir la lección.",
|
||||||
|
"createCourseError": "No se pudo crear el curso.",
|
||||||
|
"deleteLessonTitle": "Eliminar lección",
|
||||||
|
"deleteLessonSuccess": "Lección eliminada correctamente.",
|
||||||
|
"deleteLessonError": "No se pudo eliminar la lección.",
|
||||||
|
"enrollCourseError": "No se pudo inscribirse en el curso.",
|
||||||
|
"editLessonPending": "La edición individual de lecciones llegará después.",
|
||||||
|
"timeToday": "hoy",
|
||||||
|
"timeSinceOneDay": "desde hace 1 día",
|
||||||
|
"timeSinceDays": "desde hace {count} días",
|
||||||
|
"reviewDueNow": "vence ahora",
|
||||||
|
"reviewDueTomorrow": "vence mañana",
|
||||||
|
"reviewDueInDays": "vence en {count} días",
|
||||||
|
"reviewDueToday": "vence hoy",
|
||||||
|
"reviewDueSinceOneDay": "vence desde hace 1 día",
|
||||||
|
"reviewDueSinceDays": "vence desde hace {count} días",
|
||||||
|
"reviewStageDay1": "Día 1",
|
||||||
|
"reviewStageDay3": "Día 3",
|
||||||
|
"reviewStageDay7": "Día 7",
|
||||||
|
"reviewStageCompleted": "Repaso completado",
|
||||||
|
"phaseQuickstart": "Inicio rápido",
|
||||||
|
"phaseDailyLife": "Vida diaria",
|
||||||
|
"phaseStabilization": "Estabilización",
|
||||||
|
"phaseDefault": "Fase de aprendizaje",
|
||||||
|
"didacticModeCoreInput": "Contenido nuevo",
|
||||||
|
"didacticModeGuidedDialogue": "Diálogo guiado",
|
||||||
|
"didacticModeContrastTraining": "Entrenamiento por contraste",
|
||||||
|
"didacticModePatternDrill": "Entrenamiento de patrones",
|
||||||
|
"didacticModeRealLifeScenario": "Escenario cotidiano",
|
||||||
|
"didacticModeIntensiveReview": "Fase de repaso",
|
||||||
|
"didacticModeCheckpoint": "Checkpoint",
|
||||||
|
"didacticModeDefault": "Unidad de aprendizaje",
|
||||||
|
"didacticModeFocusDefault": "Foco de aprendizaje",
|
||||||
|
"lessonMetaFocus": "Enfoque",
|
||||||
|
"lessonMetaPhase": "Fase",
|
||||||
|
"lessonMetaNewUnits": "Nuevas unidades",
|
||||||
|
"lessonMetaReview": "Repaso",
|
||||||
|
"intensiveReviewTitle": "Fase de repaso intensivo",
|
||||||
|
"intensiveReviewIntro": "Esta lección da prioridad al repaso y la consolidación. El material nuevo se reduce de forma consciente para estabilizar los patrones ya conocidos.",
|
||||||
|
"reviewPriorityTitle": "El repaso se mezcla paso a paso",
|
||||||
|
"reviewPriorityIntro": "Primero el foco está en los términos nuevos de esta lección. Con tu progreso se van mezclando cada vez más vocablos anteriores.",
|
||||||
|
"exerciseLockTitle": "La prueba del capítulo sigue bloqueada",
|
||||||
|
"trainerStartWithReview": "Empieza con el vocabulario nuevo de esta lección. A medida que avances, el entrenador mezclará automáticamente repasos adecuados.",
|
||||||
|
"startLesson": "Empezar lección",
|
||||||
|
"trainerProgressNewContent": "Contenido nuevo: {current}/{target}",
|
||||||
|
"trainerProgressReview": "Repaso: {count}",
|
||||||
|
"trainerProgressMixShare": "Parte mezclada: {percent}%",
|
||||||
|
"unknownExerciseTypeNotice": "Este tipo de ejercicio todavía no se muestra de forma interactiva en la vista actual.",
|
||||||
|
"unknownExerciseTypeLabel": "Tipo: {type}",
|
||||||
|
"lessonReviewHeadlineDone": "Esta lección ya ha llegado a la fase de práctica libre.",
|
||||||
|
"lessonReviewHeadlineDue": "Esta ola de repaso está pendiente ahora mismo.",
|
||||||
|
"lessonReviewHeadlineScheduled": "Esta lección está prevista para la siguiente ola de repaso.",
|
||||||
|
"lessonReviewHintDone": "El ciclo de repaso de 1/3/7 días está completado. Ahora puedes seguir practicando esta lección libremente.",
|
||||||
|
"lessonReviewHintNextDue": "Próximo vencimiento: {due}.",
|
||||||
|
"reviewTimeNow": "ahora",
|
||||||
|
"reviewTimeTomorrow": "mañana",
|
||||||
|
"reviewTimeInDays": "en {count} días"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,12 +51,26 @@ function getBrowserLanguage() {
|
|||||||
return 'en';
|
return 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SUPPORTED_UI_LOCALES = ['de', 'en', 'ceb', 'es'];
|
||||||
|
|
||||||
|
function getInitialAppLanguage() {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem('uiLanguage');
|
||||||
|
if (saved && SUPPORTED_UI_LOCALES.includes(saved)) {
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return getBrowserLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
const vuetify = createVuetify({
|
const vuetify = createVuetify({
|
||||||
components,
|
components,
|
||||||
directives,
|
directives,
|
||||||
});
|
});
|
||||||
|
|
||||||
store.dispatch('setLanguage', getBrowserLanguage());
|
store.dispatch('setLanguage', getInitialAppLanguage());
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ function clearAuthStorage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SUPPORTED_UI_LOCALES = ['de', 'en', 'ceb', 'es'];
|
||||||
|
|
||||||
function persistAuthStorage(user, rememberMe) {
|
function persistAuthStorage(user, rememberMe) {
|
||||||
const targetStorage = rememberMe ? localStorage : sessionStorage;
|
const targetStorage = rememberMe ? localStorage : sessionStorage;
|
||||||
clearAuthStorage();
|
clearAuthStorage();
|
||||||
@@ -38,6 +40,15 @@ function persistAuthStorage(user, rememberMe) {
|
|||||||
targetStorage.setItem('userid', user?.id || '');
|
targetStorage.setItem('userid', user?.id || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readPersistedUiLanguage() {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem('uiLanguage');
|
||||||
|
return saved && SUPPORTED_UI_LOCALES.includes(saved) ? saved : null;
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const store = createStore({
|
const store = createStore({
|
||||||
state: {
|
state: {
|
||||||
isLoggedIn: getStoredValue('isLoggedIn') === 'true',
|
isLoggedIn: getStoredValue('isLoggedIn') === 'true',
|
||||||
@@ -52,7 +63,10 @@ const store = createStore({
|
|||||||
backendConnecting: false,
|
backendConnecting: false,
|
||||||
daemonConnecting: false,
|
daemonConnecting: false,
|
||||||
language: (() => {
|
language: (() => {
|
||||||
// Verwende die gleiche Logik wie in main.js
|
const persisted = readPersistedUiLanguage();
|
||||||
|
if (persisted) {
|
||||||
|
return persisted;
|
||||||
|
}
|
||||||
const browserLanguage = navigator.language || navigator.languages[0];
|
const browserLanguage = navigator.language || navigator.languages[0];
|
||||||
|
|
||||||
if (browserLanguage.startsWith('ceb') || browserLanguage.startsWith('bis')) {
|
if (browserLanguage.startsWith('ceb') || browserLanguage.startsWith('bis')) {
|
||||||
@@ -99,6 +113,15 @@ const store = createStore({
|
|||||||
state.user = user;
|
state.user = user;
|
||||||
persistAuthStorage(user, rememberMe);
|
persistAuthStorage(user, rememberMe);
|
||||||
state.menuNeedsUpdate = true;
|
state.menuNeedsUpdate = true;
|
||||||
|
const langParam = user.param?.find((p) => p.name === 'language');
|
||||||
|
if (langParam?.value && SUPPORTED_UI_LOCALES.includes(langParam.value)) {
|
||||||
|
state.language = langParam.value;
|
||||||
|
try {
|
||||||
|
localStorage.setItem('uiLanguage', langParam.value);
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
if (user.param.filter(param => ['birthdate', 'gender'].includes(param.name)).length < 2) {
|
if (user.param.filter(param => ['birthdate', 'gender'].includes(param.name)).length < 2) {
|
||||||
router.push({ path: '/settings/personal' });
|
router.push({ path: '/settings/personal' });
|
||||||
}
|
}
|
||||||
@@ -110,6 +133,11 @@ const store = createStore({
|
|||||||
localStorage.removeItem('menu');
|
localStorage.removeItem('menu');
|
||||||
state.menuNeedsUpdate = false;
|
state.menuNeedsUpdate = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.removeItem('uiLanguage');
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
// Setze die Sprache auf die Browser-Sprache zurück
|
// Setze die Sprache auf die Browser-Sprache zurück
|
||||||
const browserLanguage = navigator.language || navigator.languages[0];
|
const browserLanguage = navigator.language || navigator.languages[0];
|
||||||
|
|
||||||
@@ -150,6 +178,11 @@ const store = createStore({
|
|||||||
},
|
},
|
||||||
setLanguage(state, language) {
|
setLanguage(state, language) {
|
||||||
state.language = language;
|
state.language = language;
|
||||||
|
try {
|
||||||
|
localStorage.setItem('uiLanguage', language);
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setMenu(state, menu) {
|
setMenu(state, menu) {
|
||||||
state.menu = menu;
|
state.menu = menu;
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<div class="home-logged-in">
|
<div class="home-logged-in">
|
||||||
<section class="dashboard-hero surface-card">
|
<section class="dashboard-hero surface-card">
|
||||||
<div class="dashboard-hero__copy">
|
<div class="dashboard-hero__copy">
|
||||||
<span class="dashboard-kicker">Dein Bereich</span>
|
<span class="dashboard-kicker">{{ $t('home.dashboard.kicker') }}</span>
|
||||||
<h1>Willkommen zurück!</h1>
|
<h1>{{ $t('home.dashboard.title') }}</h1>
|
||||||
<p class="dashboard-subtitle">
|
<p class="dashboard-subtitle">
|
||||||
Dein persönlicher Einstieg in Community, Termine, Falukant und laufende Aktivitäten.
|
{{ $t('home.dashboard.subtitle') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="dashboard-toolbar surface-card">
|
<div class="dashboard-toolbar surface-card">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
class="btn-edit"
|
class="btn-edit"
|
||||||
@click="editMode = true"
|
@click="editMode = true"
|
||||||
>
|
>
|
||||||
Dashboard bearbeiten
|
{{ $t('home.dashboard.edit') }}
|
||||||
</button>
|
</button>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="widget-add-row">
|
<div class="widget-add-row">
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
class="widget-type-select"
|
class="widget-type-select"
|
||||||
@change="onSelectWidgetType"
|
@change="onSelectWidgetType"
|
||||||
>
|
>
|
||||||
<option value="">+ Widget hinzufügen …</option>
|
<option value="">{{ $t('home.dashboard.addWidget') }}</option>
|
||||||
<option
|
<option
|
||||||
v-for="wt in widgetTypeOptions"
|
v-for="wt in widgetTypeOptions"
|
||||||
:key="wt.id"
|
:key="wt.id"
|
||||||
@@ -39,11 +39,11 @@
|
|||||||
class="btn-add-again"
|
class="btn-add-again"
|
||||||
@click="addSameWidgetType"
|
@click="addSameWidgetType"
|
||||||
>
|
>
|
||||||
Nochmal hinzufügen
|
{{ $t('home.dashboard.addAgain') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-done" @click="doneEditing">
|
<button type="button" class="btn-done" @click="doneEditing">
|
||||||
Fertig
|
{{ $t('home.dashboard.done') }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,19 +51,19 @@
|
|||||||
|
|
||||||
<section class="dashboard-overview">
|
<section class="dashboard-overview">
|
||||||
<article class="overview-card surface-card">
|
<article class="overview-card surface-card">
|
||||||
<span class="overview-card__label">Aktive Widgets</span>
|
<span class="overview-card__label">{{ $t('home.dashboard.overview.activeWidgetsLabel') }}</span>
|
||||||
<strong>{{ widgets.length }}</strong>
|
<strong>{{ widgets.length }}</strong>
|
||||||
<p>Dein Dashboard ist modular aufgebaut und kann jederzeit umsortiert werden.</p>
|
<p>{{ $t('home.dashboard.overview.activeWidgetsText') }}</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="overview-card surface-card">
|
<article class="overview-card surface-card">
|
||||||
<span class="overview-card__label">Verfügbare Module</span>
|
<span class="overview-card__label">{{ $t('home.dashboard.overview.availableModulesLabel') }}</span>
|
||||||
<strong>{{ widgetTypeOptions.length }}</strong>
|
<strong>{{ widgetTypeOptions.length }}</strong>
|
||||||
<p>Du kannst Community-, Kalender-, News- und Falukant-Module kombinieren.</p>
|
<p>{{ $t('home.dashboard.overview.availableModulesText') }}</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="overview-card surface-card">
|
<article class="overview-card surface-card">
|
||||||
<span class="overview-card__label">Bearbeitungsmodus</span>
|
<span class="overview-card__label">{{ $t('home.dashboard.overview.editModeLabel') }}</span>
|
||||||
<strong>{{ editMode ? 'Aktiv' : 'Aus' }}</strong>
|
<strong>{{ editMode ? $t('home.dashboard.overview.editModeActive') : $t('home.dashboard.overview.editModeInactive') }}</strong>
|
||||||
<p>{{ editMode ? 'Widgets können gerade ergänzt und angepasst werden.' : 'Inhalte bleiben fokussiert und ruhig lesbar.' }}</p>
|
<p>{{ editMode ? $t('home.dashboard.overview.editModeActiveText') : $t('home.dashboard.overview.editModeInactiveText') }}</p>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -85,8 +85,8 @@
|
|||||||
>
|
>
|
||||||
<div class="dashboard-shell__header">
|
<div class="dashboard-shell__header">
|
||||||
<div>
|
<div>
|
||||||
<h2>Deine Übersicht</h2>
|
<h2>{{ $t('home.dashboard.sectionTitle') }}</h2>
|
||||||
<p>Widgets lassen sich verschieben und im Bearbeitungsmodus anpassen.</p>
|
<p>{{ $t('home.dashboard.sectionIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -120,17 +120,17 @@
|
|||||||
<input
|
<input
|
||||||
v-model="w.title"
|
v-model="w.title"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Titel"
|
:placeholder="$t('home.dashboard.widgetTitlePlaceholder')"
|
||||||
class="widget-edit-input"
|
class="widget-edit-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn-remove"
|
class="btn-remove"
|
||||||
title="Widget entfernen"
|
:title="$t('home.dashboard.removeWidget')"
|
||||||
@click="removeWidget(index)"
|
@click="removeWidget(index)"
|
||||||
>
|
>
|
||||||
Entfernen
|
{{ $t('home.dashboard.remove') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="widgets.length === 0 && !loading" class="dashboard-empty">
|
<div v-if="widgets.length === 0 && !loading" class="dashboard-empty">
|
||||||
<p>Noch keine Widgets. Klicke auf „Dashboard bearbeiten“ und dann „+ Widget hinzufügen“.</p>
|
<p>{{ $t('home.dashboard.empty') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
@@ -164,7 +164,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
widgetTypeOptions() {
|
widgetTypeOptions() {
|
||||||
if (this.availableWidgets.length > 0) return this.availableWidgets;
|
if (this.availableWidgets.length > 0) return this.availableWidgets;
|
||||||
return [{ id: 'default', label: 'Termine', endpoint: '/api/termine' }];
|
return [{ id: 'default', label: this.$t('home.dashboard.defaultAppointmentsWidget'), endpoint: '/api/termine' }];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -185,6 +185,41 @@ export default {
|
|||||||
this.loadAvailableWidgets();
|
this.loadAvailableWidgets();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getLocalizedWidgetLabel(endpoint, fallbackLabel = '') {
|
||||||
|
const key = {
|
||||||
|
'/api/termine': 'home.dashboard.widgetLabels.appointments',
|
||||||
|
'/api/falukant/dashboard-widget': 'home.dashboard.widgetLabels.falukant',
|
||||||
|
'/api/news': 'home.dashboard.widgetLabels.news',
|
||||||
|
'/api/calendar/widget/birthdays': 'home.dashboard.widgetLabels.birthdays',
|
||||||
|
'/api/calendar/widget/upcoming': 'home.dashboard.widgetLabels.upcoming',
|
||||||
|
'/api/calendar/widget/mini': 'home.dashboard.widgetLabels.calendar'
|
||||||
|
}[endpoint];
|
||||||
|
return key ? this.$t(key) : fallbackLabel;
|
||||||
|
},
|
||||||
|
normalizeWidgetType(widgetType) {
|
||||||
|
return {
|
||||||
|
...widgetType,
|
||||||
|
label: this.getLocalizedWidgetLabel(widgetType?.endpoint, widgetType?.label || '')
|
||||||
|
};
|
||||||
|
},
|
||||||
|
normalizeWidgetConfig(widget) {
|
||||||
|
const localizedLabel = this.getLocalizedWidgetLabel(widget?.endpoint, '');
|
||||||
|
const title = String(widget?.title || '').trim();
|
||||||
|
const knownDefaultLabels = [
|
||||||
|
'Termine',
|
||||||
|
'Falukant',
|
||||||
|
'News',
|
||||||
|
'Geburtstage',
|
||||||
|
'Nächste Termine',
|
||||||
|
'Kalender'
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
...widget,
|
||||||
|
title: !title || knownDefaultLabels.includes(title)
|
||||||
|
? (localizedLabel || title)
|
||||||
|
: title
|
||||||
|
};
|
||||||
|
},
|
||||||
/** Endpoint aus Widget-Typ (anhand gespeichertem endpoint gematcht), sonst w.endpoint. */
|
/** Endpoint aus Widget-Typ (anhand gespeichertem endpoint gematcht), sonst w.endpoint. */
|
||||||
effectiveEndpoint(w) {
|
effectiveEndpoint(w) {
|
||||||
if (!w?.endpoint) return '';
|
if (!w?.endpoint) return '';
|
||||||
@@ -204,7 +239,7 @@ export default {
|
|||||||
async loadAvailableWidgets() {
|
async loadAvailableWidgets() {
|
||||||
try {
|
try {
|
||||||
const { data } = await apiClient.get('/api/dashboard/widgets');
|
const { data } = await apiClient.get('/api/dashboard/widgets');
|
||||||
this.availableWidgets = Array.isArray(data) ? data : [];
|
this.availableWidgets = Array.isArray(data) ? data.map(widget => this.normalizeWidgetType(widget)) : [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.availableWidgets = [];
|
this.availableWidgets = [];
|
||||||
}
|
}
|
||||||
@@ -216,14 +251,14 @@ export default {
|
|||||||
const { data } = await apiClient.get('/api/dashboard/config');
|
const { data } = await apiClient.get('/api/dashboard/config');
|
||||||
let list = Array.isArray(data?.widgets) ? [...data.widgets] : [];
|
let list = Array.isArray(data?.widgets) ? [...data.widgets] : [];
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
list = [{ id: generateId(), title: 'Termine', endpoint: '/api/termine' }];
|
list = [{ id: generateId(), title: this.$t('home.dashboard.defaultAppointmentsWidget'), endpoint: '/api/termine' }];
|
||||||
this.widgets = list;
|
this.widgets = list;
|
||||||
await this.saveConfig();
|
await this.saveConfig();
|
||||||
} else {
|
} else {
|
||||||
this.widgets = list;
|
this.widgets = list.map(widget => this.normalizeWidgetConfig(widget));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.loadError = e.response?.data?.error || e.message || 'Dashboard konnte nicht geladen werden.';
|
this.loadError = e.response?.data?.error || e.message || this.$t('home.dashboard.loadError');
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@@ -239,7 +274,7 @@ export default {
|
|||||||
await apiClient.put('/api/dashboard/config', { widgets: payload });
|
await apiClient.put('/api/dashboard/config', { widgets: payload });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Dashboard speichern fehlgeschlagen:', e);
|
console.error('Dashboard speichern fehlgeschlagen:', e);
|
||||||
this.saveError = e.response?.data?.error || e.message || 'Dashboard konnte nicht gespeichert werden.';
|
this.saveError = e.response?.data?.error || e.message || this.$t('home.dashboard.saveError');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addWidgetFromType(wt) {
|
addWidgetFromType(wt) {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<div class="calendar-view">
|
<div class="calendar-view">
|
||||||
<section class="calendar-hero surface-card">
|
<section class="calendar-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="calendar-kicker">Planung</span>
|
<span class="calendar-kicker">{{ $t('personal.calendar.kicker') }}</span>
|
||||||
<h2>{{ $t('personal.calendar.title') }}</h2>
|
<h2>{{ $t('personal.calendar.title') }}</h2>
|
||||||
<p>Termine, Geburtstage und eigene Einträge in einer strukturierten Übersicht.</p>
|
<p>{{ $t('personal.calendar.intro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="marketing-page">
|
<section class="marketing-page">
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<p class="eyebrow">Sprachen online lernen</p>
|
<p class="eyebrow">{{ $t('home.vocabLanding.eyebrow') }}</p>
|
||||||
<h1>Der Vokabeltrainer auf YourPart kombiniert Lernen, Kurse und Übungen in einer Plattform.</h1>
|
<h1>{{ $t('home.vocabLanding.title') }}</h1>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
Arbeite mit interaktiven Lektionen, erweitere deinen Wortschatz und nutze strukturierte Inhalte für einen
|
{{ $t('home.vocabLanding.lead') }}
|
||||||
motivierenden Lernfluss direkt im Browser.
|
|
||||||
</p>
|
</p>
|
||||||
<router-link class="cta" to="/">Kostenlos starten</router-link>
|
<router-link class="cta" to="/">{{ $t('home.vocabLanding.cta') }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="features">
|
<div class="features">
|
||||||
<article>
|
<article>
|
||||||
<h2>Interaktive Kurse</h2>
|
<h2>{{ $t('home.vocabLanding.feature1Title') }}</h2>
|
||||||
<p>Kurse, Lektionen und Übungen helfen beim systematischen Aufbau neuer Sprachkenntnisse.</p>
|
<p>{{ $t('home.vocabLanding.feature1Text') }}</p>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<h2>Praxisorientiert</h2>
|
<h2>{{ $t('home.vocabLanding.feature2Title') }}</h2>
|
||||||
<p>Wortschatz, Grammatik und Wiederholung werden auf eine alltagstaugliche Lernroutine ausgerichtet.</p>
|
<p>{{ $t('home.vocabLanding.feature2Text') }}</p>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<h2>Teil einer Community</h2>
|
<h2>{{ $t('home.vocabLanding.feature3Title') }}</h2>
|
||||||
<p>Der Sprachbereich ist in eine größere Community-Plattform mit Blogs, Forum und Chat eingebettet.</p>
|
<p>{{ $t('home.vocabLanding.feature3Text') }}</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VocabLandingView'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.marketing-page {
|
.marketing-page {
|
||||||
max-width: 1100px;
|
max-width: 1100px;
|
||||||
|
|||||||
@@ -228,6 +228,11 @@ export default {
|
|||||||
display: grid;
|
display: grid;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
max-width: 960px;
|
max-width: 960px;
|
||||||
|
/* Flex-Kind von .app-content__inner: ohne overflow wird Inhalt bei min-height:0 abgeschnitten */
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
align-content: start;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-settings__hero,
|
.account-settings__hero,
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
<div class="diary-view">
|
<div class="diary-view">
|
||||||
<section class="diary-hero surface-card">
|
<section class="diary-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="diary-kicker">Persönliche Einträge</span>
|
<span class="diary-kicker">{{ $t('socialnetwork.diary.kicker') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.diary.title') }}</h2>
|
<h2>{{ $t('socialnetwork.diary.title') }}</h2>
|
||||||
<p>Gedanken, Notizen und kurze Updates in einer ruhigen, persönlichen Ansicht.</p>
|
<p>{{ $t('socialnetwork.diary.intro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="new-entry-section surface-card">
|
<section class="new-entry-section surface-card">
|
||||||
<h3>{{ isEditing ? $t('socialnetwork.diary.editEntry') : $t('socialnetwork.diary.newEntry') }}</h3>
|
<h3>{{ isEditing ? $t('socialnetwork.diary.editEntry') : $t('socialnetwork.diary.newEntry') }}</h3>
|
||||||
<textarea v-model="newEntryText" placeholder="Write your diary entry..."></textarea>
|
<textarea v-model="newEntryText" :placeholder="$t('socialnetwork.diary.placeholder')"></textarea>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button @click="saveEntry">{{ isEditing ? $t('socialnetwork.diary.update') : $t('socialnetwork.diary.save')
|
<button @click="saveEntry">{{ isEditing ? $t('socialnetwork.diary.update') : $t('socialnetwork.diary.save')
|
||||||
}}</button>
|
}}</button>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<label for="imageFile">{{ $t('socialnetwork.gallery.upload.image_file') }}</label>
|
<label for="imageFile">{{ $t('socialnetwork.gallery.upload.image_file') }}</label>
|
||||||
<input type="file" accept="image/*" required @change="onFileChange" />
|
<input type="file" accept="image/*" required @change="onFileChange" />
|
||||||
<div v-if="imagePreview" class="image-preview">
|
<div v-if="imagePreview" class="image-preview">
|
||||||
<img :src="imagePreview" alt="Image Preview" style="max-width: 150px; max-height: 150px;" />
|
<img :src="imagePreview" :alt="$t('socialnetwork.erotic.imagePreviewAlt')" style="max-width: 150px; max-height: 150px;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
id="selectedUsers"
|
id="selectedUsers"
|
||||||
v-model="selectedUsernamesText"
|
v-model="selectedUsernamesText"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="anna, bert, clara"
|
:placeholder="$t('socialnetwork.erotic.selectedUsersPlaceholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
<ul v-if="images.length > 0" class="image-grid">
|
<ul v-if="images.length > 0" class="image-grid">
|
||||||
<li v-for="image in images" :key="image.id" class="erotic-image-card">
|
<li v-for="image in images" :key="image.id" class="erotic-image-card">
|
||||||
<div class="erotic-image-card__preview" @click="!image.isModeratedHidden && openImageDialog(image)">
|
<div class="erotic-image-card__preview" @click="!image.isModeratedHidden && openImageDialog(image)">
|
||||||
<img v-if="!image.isModeratedHidden" :src="image.url || image.placeholder" alt="Loading..." />
|
<img v-if="!image.isModeratedHidden" :src="image.url || image.placeholder" :alt="$t('socialnetwork.erotic.imageLoadingAlt')" />
|
||||||
<div v-else class="erotic-image-card__hidden">
|
<div v-else class="erotic-image-card__hidden">
|
||||||
{{ $t('socialnetwork.erotic.hiddenByModeration') }}
|
{{ $t('socialnetwork.erotic.hiddenByModeration') }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<span class="erotic-videos-eyebrow">{{ $t('socialnetwork.erotic.eyebrow') }}</span>
|
<span class="erotic-videos-eyebrow">{{ $t('socialnetwork.erotic.eyebrow') }}</span>
|
||||||
<h2>{{ isForeignView ? `${$t('socialnetwork.erotic.videosTitle')} · ${viewUsername}` : $t('socialnetwork.erotic.videosTitle') }}</h2>
|
<h2>{{ isForeignView ? `${$t('socialnetwork.erotic.videosTitle')} · ${viewUsername}` : $t('socialnetwork.erotic.videosTitle') }}</h2>
|
||||||
<p>{{ isForeignView ? 'Freigegebene Videos aus dem Erwachsenenbereich.' : $t('socialnetwork.erotic.videosIntro') }}</p>
|
<p>{{ isForeignView ? $t('socialnetwork.erotic.foreignVideosIntro') : $t('socialnetwork.erotic.videosIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -53,14 +53,14 @@
|
|||||||
</label>
|
</label>
|
||||||
<label v-if="requiresSelectedUsers">
|
<label v-if="requiresSelectedUsers">
|
||||||
<span>{{ $t('socialnetwork.gallery.visibility.selected-users') }}</span>
|
<span>{{ $t('socialnetwork.gallery.visibility.selected-users') }}</span>
|
||||||
<input v-model="selectedUsernamesText" type="text" placeholder="anna, bert, clara" />
|
<input v-model="selectedUsernamesText" type="text" :placeholder="$t('socialnetwork.erotic.selectedUsersPlaceholder')" />
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>{{ $t('socialnetwork.erotic.videoFile') }}</span>
|
<span>{{ $t('socialnetwork.erotic.videoFile') }}</span>
|
||||||
<input type="file" accept="video/mp4,video/webm,video/ogg,video/quicktime" required @change="onFileChange" />
|
<input type="file" accept="video/mp4,video/webm,video/ogg,video/quicktime" required @change="onFileChange" />
|
||||||
</label>
|
</label>
|
||||||
<div class="erotic-videos-upload__meta">
|
<div class="erotic-videos-upload__meta">
|
||||||
<span>MP4, WEBM, OGG, MOV</span>
|
<span>{{ $t('socialnetwork.erotic.videoFormats') }}</span>
|
||||||
<span v-if="fileToUpload">{{ fileToUpload.name }}</span>
|
<span v-if="fileToUpload">{{ fileToUpload.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">{{ $t('socialnetwork.gallery.upload.upload_button') }}</button>
|
<button type="submit">{{ $t('socialnetwork.gallery.upload.upload_button') }}</button>
|
||||||
@@ -68,34 +68,34 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="erotic-videos-panel surface-card">
|
<section class="erotic-videos-panel surface-card">
|
||||||
<h3>Bibliothek</h3>
|
<h3>{{ $t('socialnetwork.erotic.libraryTitle') }}</h3>
|
||||||
<div class="erotic-videos-panel__list">
|
<div class="erotic-videos-panel__list">
|
||||||
<div class="erotic-videos-panel__item">
|
<div class="erotic-videos-panel__item">
|
||||||
<span>{{ isForeignView ? 'Freigegebene Videos' : $t('socialnetwork.erotic.myVideos') }}</span>
|
<span>{{ isForeignView ? $t('socialnetwork.erotic.sharedVideos') : $t('socialnetwork.erotic.myVideos') }}</span>
|
||||||
<strong>{{ videos.length }}</strong>
|
<strong>{{ videos.length }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="erotic-videos-panel__item">
|
<div class="erotic-videos-panel__item">
|
||||||
<span>Letzter Upload</span>
|
<span>{{ $t('socialnetwork.erotic.latestUpload') }}</span>
|
||||||
<strong>{{ latestVideoTitle }}</strong>
|
<strong>{{ latestVideoTitle }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="erotic-videos-panel__item">
|
<div class="erotic-videos-panel__item">
|
||||||
<span>Sichtbare Videos</span>
|
<span>{{ $t('socialnetwork.erotic.visibleVideos') }}</span>
|
||||||
<strong>{{ visibleVideosCount }}</strong>
|
<strong>{{ visibleVideosCount }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="erotic-videos-panel__item">
|
<div class="erotic-videos-panel__item">
|
||||||
<span>Moderationsfälle</span>
|
<span>{{ $t('socialnetwork.erotic.moderationCases') }}</span>
|
||||||
<strong>{{ hiddenVideosCount }}</strong>
|
<strong>{{ hiddenVideosCount }}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="erotic-videos-panel surface-card">
|
<section class="erotic-videos-panel surface-card">
|
||||||
<h3>Hinweise</h3>
|
<h3>{{ $t('socialnetwork.erotic.notesTitle') }}</h3>
|
||||||
<ul class="erotic-videos-checklist">
|
<ul class="erotic-videos-checklist">
|
||||||
<li>{{ isForeignView ? 'Du siehst hier nur Videos, die dir für den Erwachsenenbereich freigegeben wurden.' : $t('socialnetwork.erotic.videoUploadHint') }}</li>
|
<li>{{ isForeignView ? $t('socialnetwork.erotic.foreignVideosOnlyHint') : $t('socialnetwork.erotic.videoUploadHint') }}</li>
|
||||||
<li>Freunde sehen Inhalte nur dann, wenn sie volljährig und für den Erwachsenenbereich freigeschaltet sind.</li>
|
<li>{{ $t('socialnetwork.erotic.friendsVisibilityHint') }}</li>
|
||||||
<li>Gezielt freigegebene Personen müssen ebenfalls volljährig und freigeschaltet sein.</li>
|
<li>{{ $t('socialnetwork.erotic.selectedUsersVisibilityHint') }}</li>
|
||||||
<li>Nutze {{ $t('socialnetwork.erotic.reportAction') }} direkt am jeweiligen Eintrag, wenn Inhalte geprüft werden sollen.</li>
|
<li>{{ $t('socialnetwork.erotic.reportHint', { action: $t('socialnetwork.erotic.reportAction') }) }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -103,15 +103,15 @@
|
|||||||
<section class="erotic-videos-library surface-card">
|
<section class="erotic-videos-library surface-card">
|
||||||
<div class="erotic-videos-library__header">
|
<div class="erotic-videos-library__header">
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ isForeignView ? 'Freigegebene Videos' : $t('socialnetwork.erotic.myVideos') }}</h3>
|
<h3>{{ isForeignView ? $t('socialnetwork.erotic.sharedVideos') : $t('socialnetwork.erotic.myVideos') }}</h3>
|
||||||
<p>{{ isForeignView ? 'Sichtbare Videos aus freigegebenen Erwachsenenbereichen.' : 'Eigene Uploads, Freigaben und Meldungen an einem Ort.' }}</p>
|
<p>{{ isForeignView ? $t('socialnetwork.erotic.sharedVideosIntro') : $t('socialnetwork.erotic.libraryIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="erotic-videos-library__count">{{ videos.length }}</span>
|
<span class="erotic-videos-library__count">{{ videos.length }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="videos.length === 0" class="erotic-videos-empty">
|
<div v-if="videos.length === 0" class="erotic-videos-empty">
|
||||||
<strong>{{ $t('socialnetwork.erotic.noVideos') }}</strong>
|
<strong>{{ $t('socialnetwork.erotic.noVideos') }}</strong>
|
||||||
<span>{{ isForeignView ? 'Für dich sind aktuell keine freigegebenen Videos vorhanden.' : 'Lege links dein erstes Video an und verwalte es danach hier in der Bibliothek.' }}</span>
|
<span>{{ isForeignView ? $t('socialnetwork.erotic.noSharedVideos') : $t('socialnetwork.erotic.libraryEmptyHint') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="erotic-videos-library__scroll">
|
<div v-else class="erotic-videos-library__scroll">
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
{{ $t('socialnetwork.erotic.hiddenByModeration') }}
|
{{ $t('socialnetwork.erotic.hiddenByModeration') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="erotic-videos-card__meta">
|
<div class="erotic-videos-card__meta">
|
||||||
<strong>{{ video.title || 'Ohne Titel' }}</strong>
|
<strong>{{ video.title || $t('socialnetwork.erotic.untitled') }}</strong>
|
||||||
<span v-if="video.createdAtLabel">{{ video.createdAtLabel }}</span>
|
<span v-if="video.createdAtLabel">{{ video.createdAtLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="video.visibilities?.length" class="erotic-videos-card__visibility">
|
<div v-if="video.visibilities?.length" class="erotic-videos-card__visibility">
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
<p v-if="video.description">{{ video.description }}</p>
|
<p v-if="video.description">{{ video.description }}</p>
|
||||||
<div v-if="!isForeignView" class="erotic-videos-card__edit">
|
<div v-if="!isForeignView" class="erotic-videos-card__edit">
|
||||||
<button type="button" class="secondary" @click="toggleEditor(video.id)">
|
<button type="button" class="secondary" @click="toggleEditor(video.id)">
|
||||||
{{ editingVideoId === video.id ? 'Bearbeitung schließen' : 'Freigaben bearbeiten' }}
|
{{ editingVideoId === video.id ? $t('socialnetwork.erotic.closeEditing') : $t('socialnetwork.erotic.editVisibility') }}
|
||||||
</button>
|
</button>
|
||||||
<div v-if="editingVideoId === video.id" class="erotic-videos-editor">
|
<div v-if="editingVideoId === video.id" class="erotic-videos-editor">
|
||||||
<label>
|
<label>
|
||||||
@@ -173,10 +173,10 @@
|
|||||||
</label>
|
</label>
|
||||||
<label v-if="editRequiresSelectedUsers">
|
<label v-if="editRequiresSelectedUsers">
|
||||||
<span>{{ $t('socialnetwork.gallery.visibility.selected-users') }}</span>
|
<span>{{ $t('socialnetwork.gallery.visibility.selected-users') }}</span>
|
||||||
<input v-model="editForm.selectedUsernamesText" type="text" placeholder="anna, bert, clara" />
|
<input v-model="editForm.selectedUsernamesText" type="text" :placeholder="$t('socialnetwork.erotic.selectedUsersPlaceholder')" />
|
||||||
</label>
|
</label>
|
||||||
<div class="erotic-videos-editor__actions">
|
<div class="erotic-videos-editor__actions">
|
||||||
<button type="button" @click="saveVideo(video.id)">Speichern</button>
|
<button type="button" @click="saveVideo(video.id)">{{ $t('common.save') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -265,7 +265,7 @@ export default {
|
|||||||
return this.videos.filter((video) => video.isModeratedHidden).length;
|
return this.videos.filter((video) => video.isModeratedHidden).length;
|
||||||
},
|
},
|
||||||
latestVideoTitle() {
|
latestVideoTitle() {
|
||||||
return this.videos[0]?.title || 'Noch kein Upload';
|
return this.videos[0]?.title || this.$t('socialnetwork.erotic.noUploadYet');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="forum-topic-back link" @click="openForum()">{{ $t('socialnetwork.forum.title') }} {{ forumName }}</div>
|
<div class="forum-topic-back link" @click="openForum()">{{ $t('socialnetwork.forum.title') }} {{ forumName }}</div>
|
||||||
<h2 v-if="forumTopic">{{ forumTopic }}</h2>
|
<h2 v-if="forumTopic">{{ forumTopic }}</h2>
|
||||||
<p>Diskussionen, Antworten und neue Beiträge in einer fokussierten Lesefläche.</p>
|
<p>{{ $t('socialnetwork.forum.topicIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<div class="forum-view">
|
<div class="forum-view">
|
||||||
<section class="forum-hero surface-card">
|
<section class="forum-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="forum-kicker">Community-Forum</span>
|
<span class="forum-kicker">{{ $t('socialnetwork.forum.kicker') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.forum.title') }} {{ forumName }}</h2>
|
<h2>{{ $t('socialnetwork.forum.title') }} {{ forumName }}</h2>
|
||||||
<p>Themen, Diskussionen und neue Beiträge an einem strukturierten Ort.</p>
|
<p>{{ $t('socialnetwork.forum.intro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="creationtoggler">
|
<div class="creationtoggler">
|
||||||
<button @click="toggleCreation">
|
<button @click="toggleCreation">
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
<section v-if="inCreation" class="forum-creation surface-card">
|
<section v-if="inCreation" class="forum-creation surface-card">
|
||||||
<div class="forum-creation__header">
|
<div class="forum-creation__header">
|
||||||
<div>
|
<div>
|
||||||
<h3>Neues Thema verfassen</h3>
|
<h3>{{ $t('socialnetwork.forum.createTitle') }}</h3>
|
||||||
<p>Erst Titel setzen, dann den Beitrag schreiben und anschließend direkt veröffentlichen.</p>
|
<p>{{ $t('socialnetwork.forum.createIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="button-secondary" @click="cancelCreation">Abbrechen</button>
|
<button type="button" class="button-secondary" @click="cancelCreation">{{ $t('socialnetwork.forum.cancelCreation') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<label class="newtitle">
|
<label class="newtitle">
|
||||||
<span>{{ $t('socialnetwork.forum.topic') }}</span>
|
<span>{{ $t('socialnetwork.forum.topic') }}</span>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<button :disabled="!canSaveTopic" @click="saveNewTopic">
|
<button :disabled="!canSaveTopic" @click="saveNewTopic">
|
||||||
{{ $t('socialnetwork.forum.createNewTopic') }}
|
{{ $t('socialnetwork.forum.createNewTopic') }}
|
||||||
</button>
|
</button>
|
||||||
<span class="forum-creation__hint">Titel und Inhalt müssen beide ausgefüllt sein.</span>
|
<span class="forum-creation__hint">{{ $t('socialnetwork.forum.creationHint') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<button type="button" class="topic-card__main" @click="openTopic(topic.id)">
|
<button type="button" class="topic-card__main" @click="openTopic(topic.id)">
|
||||||
<strong>{{ topic.title }}</strong>
|
<strong>{{ topic.title }}</strong>
|
||||||
<span class="topic-card__meta">
|
<span class="topic-card__meta">
|
||||||
{{ topic.user?.username || topic.owner?.username || 'Community' }}
|
{{ topic.user?.username || topic.owner?.username || $t('socialnetwork.forum.communityFallback') }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -175,10 +175,10 @@ export default {
|
|||||||
this.inCreation = false
|
this.inCreation = false
|
||||||
this.newTitle = ''
|
this.newTitle = ''
|
||||||
this.editor?.commands.setContent('')
|
this.editor?.commands.setContent('')
|
||||||
showSuccess(this, 'Thema erfolgreich erstellt.')
|
showSuccess(this, this.$t('socialnetwork.forum.topicCreated'))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Fehler beim Erstellen des Themas', err)
|
console.error('Fehler beim Erstellen des Themas', err)
|
||||||
showApiError(this, err, 'Fehler beim Erstellen des Themas')
|
showApiError(this, err, this.$t('socialnetwork.forum.topicCreateError'))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goToPage(page) {
|
goToPage(page) {
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
<div class="friends-view">
|
<div class="friends-view">
|
||||||
<section class="friends-hero surface-card">
|
<section class="friends-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="friends-kicker">Community</span>
|
<span class="friends-kicker">{{ $t('friends.kicker') }}</span>
|
||||||
<h2>{{ $t('friends.title') }}</h2>
|
<h2>{{ $t('friends.title') }}</h2>
|
||||||
<p>Freundschaften, offene Anfragen und laufende Kontakte an einem Ort.</p>
|
<p>{{ $t('friends.intro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="friends-stats">
|
<div class="friends-stats">
|
||||||
<div class="friends-stat surface-card">
|
<div class="friends-stat surface-card">
|
||||||
<strong>{{ tabs[0].data.length }}</strong>
|
<strong>{{ tabs[0].data.length }}</strong>
|
||||||
<span>Bestehend</span>
|
<span>{{ $t('friends.stats.existing') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="friends-stat surface-card">
|
<div class="friends-stat surface-card">
|
||||||
<strong>{{ tabs[1].data.length + tabs[2].data.length }}</strong>
|
<strong>{{ tabs[1].data.length + tabs[2].data.length }}</strong>
|
||||||
<span>Offen</span>
|
<span>{{ $t('friends.stats.open') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<div class="gallery-page">
|
<div class="gallery-page">
|
||||||
<section class="gallery-hero surface-card">
|
<section class="gallery-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="gallery-kicker">Bilder und Ordner</span>
|
<span class="gallery-kicker">{{ $t('socialnetwork.gallery.kicker') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.gallery.title') }}</h2>
|
<h2>{{ $t('socialnetwork.gallery.title') }}</h2>
|
||||||
<p>Eigene Inhalte organisieren, sichtbar machen und in Ordnern strukturieren.</p>
|
<p>{{ $t('socialnetwork.gallery.intro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="gallery-view">
|
<div class="gallery-view">
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<label for="imageFile">{{ $t('socialnetwork.gallery.upload.image_file') }}</label>
|
<label for="imageFile">{{ $t('socialnetwork.gallery.upload.image_file') }}</label>
|
||||||
<input type="file" @change="onFileChange" accept="image/*" required />
|
<input type="file" @change="onFileChange" accept="image/*" required />
|
||||||
<div v-if="imagePreview" class="image-preview">
|
<div v-if="imagePreview" class="image-preview">
|
||||||
<img :src="imagePreview" alt="Image Preview"
|
<img :src="imagePreview" :alt="$t('socialnetwork.gallery.imagePreviewAlt')"
|
||||||
style="max-width: 150px; max-height: 150px;" />
|
style="max-width: 150px; max-height: 150px;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
<h3>{{ $t('socialnetwork.gallery.images') }}</h3>
|
<h3>{{ $t('socialnetwork.gallery.images') }}</h3>
|
||||||
<ul v-if="images.length > 0" class="image-grid">
|
<ul v-if="images.length > 0" class="image-grid">
|
||||||
<li v-for="image in images" :key="image.id" @click="openImageDialog(image)">
|
<li v-for="image in images" :key="image.id" @click="openImageDialog(image)">
|
||||||
<img :src="image.url || image.placeholder" alt="Loading..." />
|
<img :src="image.url || image.placeholder" :alt="$t('socialnetwork.gallery.imageLoadingAlt')" />
|
||||||
<p>{{ image.title }}</p>
|
<p>{{ image.title }}</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<div class="guestbook-view">
|
<div class="guestbook-view">
|
||||||
<section class="guestbook-hero surface-card">
|
<section class="guestbook-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="guestbook-kicker">Gästebuch</span>
|
<span class="guestbook-kicker">{{ $t('socialnetwork.guestbook.kicker') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.guestbook.title') }}</h2>
|
<h2>{{ $t('socialnetwork.guestbook.title') }}</h2>
|
||||||
<p>Nachrichten, Rückmeldungen und kleine Einblicke aus deinem Netzwerk.</p>
|
<p>{{ $t('socialnetwork.guestbook.intro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div v-if="guestbookEntries.length === 0" class="guestbook-empty surface-card">{{ $t('socialnetwork.profile.guestbook.noEntries') }}
|
<div v-if="guestbookEntries.length === 0" class="guestbook-empty surface-card">{{ $t('socialnetwork.profile.guestbook.noEntries') }}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<div class="search-view">
|
<div class="search-view">
|
||||||
<section class="search-hero surface-card">
|
<section class="search-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="search-kicker">Community-Suche</span>
|
<span class="search-kicker">{{ $t('socialnetwork.usersearch.kicker') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.usersearch.title') }}</h2>
|
<h2>{{ $t('socialnetwork.usersearch.title') }}</h2>
|
||||||
<p>Mit Namen, Alter und Geschlecht gezielt passende Kontakte in der Community finden.</p>
|
<p>{{ $t('socialnetwork.usersearch.intro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="age-range">
|
<div class="age-range">
|
||||||
<input type="number" id="ageFrom" v-model="searchCriteria.ageFrom" :min="14" :max="150"
|
<input type="number" id="ageFrom" v-model="searchCriteria.ageFrom" :min="14" :max="150"
|
||||||
:placeholder="$t('socialnetwork.usersearch.age_from')" class="age-input" />
|
:placeholder="$t('socialnetwork.usersearch.age_from')" class="age-input" />
|
||||||
<span class="age-separator">bis</span>
|
<span class="age-separator">{{ $t('socialnetwork.usersearch.ageSeparator') }}</span>
|
||||||
<input type="number" id="ageTo" v-model="searchCriteria.ageTo" :min="14" :max="150"
|
<input type="number" id="ageTo" v-model="searchCriteria.ageTo" :min="14" :max="150"
|
||||||
:placeholder="$t('socialnetwork.usersearch.age_to')" class="age-input" />
|
:placeholder="$t('socialnetwork.usersearch.age_to')" class="age-input" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<section class="search-results surface-card" v-if="searchResults.length">
|
<section class="search-results surface-card" v-if="searchResults.length">
|
||||||
<div class="results-header">
|
<div class="results-header">
|
||||||
<h3>{{ $t('socialnetwork.usersearch.results_title') }}</h3>
|
<h3>{{ $t('socialnetwork.usersearch.results_title') }}</h3>
|
||||||
<span class="results-count">{{ searchResults.length }} Treffer</span>
|
<span class="results-count">{{ $t('socialnetwork.usersearch.resultsCount', { count: searchResults.length }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="result-cards">
|
<div class="result-cards">
|
||||||
<article v-for="result in searchResults" :key="result.id" class="result-card">
|
<article v-for="result in searchResults" :key="result.id" class="result-card">
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="button-secondary" @click="openUserProfile(result.id)">
|
<button type="button" class="button-secondary" @click="openUserProfile(result.id)">
|
||||||
Profil öffnen
|
{{ $t('socialnetwork.usersearch.openProfile') }}
|
||||||
</button>
|
</button>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vocab-chapter-view">
|
<div class="vocab-chapter-view">
|
||||||
<section class="vocab-chapter-hero surface-card">
|
<section class="vocab-chapter-hero surface-card">
|
||||||
<span class="vocab-chapter-hero__eyebrow">Vokabeltrainer</span>
|
<span class="vocab-chapter-hero__eyebrow">{{ $t('socialnetwork.vocab.chapterHeroEyebrow') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.vocab.chapterTitle', { title: chapter?.title || '' }) }}</h2>
|
<h2>{{ $t('socialnetwork.vocab.chapterTitle', { title: chapter?.title || '' }) }}</h2>
|
||||||
<p>Kapitelinhalt durchsuchen, Vokabeln pflegen und direkt in die Übung wechseln.</p>
|
<p>{{ $t('socialnetwork.vocab.chapterHeroIntro') }}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="box surface-card">
|
<section class="box surface-card">
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<div class="vocab-course-list">
|
<div class="vocab-course-list">
|
||||||
<section class="vocab-courses-hero surface-card">
|
<section class="vocab-courses-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="vocab-courses-kicker">Kurse</span>
|
<span class="vocab-courses-kicker">{{ $t('socialnetwork.vocab.courses.courseListKicker') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.vocab.courses.title') }}</h2>
|
<h2>{{ $t('socialnetwork.vocab.courses.title') }}</h2>
|
||||||
<p>Öffentliche und eigene Lernkurse filtern, finden und direkt weiterlernen.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.courseListIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
<form @submit.prevent="findCourseByCode">
|
<form @submit.prevent="findCourseByCode">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('socialnetwork.vocab.courses.shareCode') }}</label>
|
<label>{{ $t('socialnetwork.vocab.courses.shareCode') }}</label>
|
||||||
<input v-model="shareCode" placeholder="z.B. abc123def456" required />
|
<input v-model="shareCode" :placeholder="$t('socialnetwork.vocab.courses.courseShareCodePlaceholder')" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button type="submit">{{ $t('general.search') }}</button>
|
<button type="submit">{{ $t('general.search') }}</button>
|
||||||
@@ -329,7 +329,7 @@ export default {
|
|||||||
await this.loadAllCourses();
|
await this.loadAllCourses();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Fehler beim Erstellen des Kurses:', e);
|
console.error('Fehler beim Erstellen des Kurses:', e);
|
||||||
showApiError(this, e, 'Fehler beim Erstellen des Kurses');
|
showApiError(this, e, this.$t('socialnetwork.vocab.courses.createCourseError'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async enroll(courseId) {
|
async enroll(courseId) {
|
||||||
@@ -339,7 +339,7 @@ export default {
|
|||||||
this.openCourse(courseId);
|
this.openCourse(courseId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Fehler beim Einschreiben:', e);
|
console.error('Fehler beim Einschreiben:', e);
|
||||||
showApiError(this, e, 'Fehler beim Einschreiben');
|
showApiError(this, e, this.$t('socialnetwork.vocab.courses.enrollCourseError'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openCourse(courseId) {
|
openCourse(courseId) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div v-else-if="course">
|
<div v-else-if="course">
|
||||||
<section class="course-hero surface-card">
|
<section class="course-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="course-kicker">Lernkurs</span>
|
<span class="course-kicker">{{ $t('socialnetwork.vocab.courses.courseKicker') }}</span>
|
||||||
<h2>{{ course.title }}</h2>
|
<h2>{{ course.title }}</h2>
|
||||||
<p v-if="course.description">{{ course.description }}</p>
|
<p v-if="course.description">{{ course.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,13 +41,13 @@
|
|||||||
<section v-if="course.lessons && course.lessons.length > 0" class="surface-card course-flow">
|
<section v-if="course.lessons && course.lessons.length > 0" class="surface-card course-flow">
|
||||||
<div class="course-flow__header">
|
<div class="course-flow__header">
|
||||||
<div>
|
<div>
|
||||||
<span class="course-flow__eyebrow">Tagesfluss</span>
|
<span class="course-flow__eyebrow">{{ $t('socialnetwork.vocab.courses.courseFlowEyebrow') }}</span>
|
||||||
<h3>Heute sinnvoll weitermachen</h3>
|
<h3>{{ $t('socialnetwork.vocab.courses.courseFlowTitle') }}</h3>
|
||||||
<p>Die Reihenfolge folgt dem Konzept: fällige Wiederholung zuerst, dann aktueller Block, danach Intensivphase und freie Vertiefung.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.courseFlowIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="course-flow__stats">
|
<div class="course-flow__stats">
|
||||||
<span class="course-flow__stat">Fällige Wiederholung: {{ dueReviewLessons.length }}</span>
|
<span class="course-flow__stat">{{ $t('socialnetwork.vocab.courses.courseFlowReviewStat', { count: dueReviewLessons.length }) }}</span>
|
||||||
<span class="course-flow__stat">Aktiver Block: {{ currentBlockNumber || '—' }}</span>
|
<span class="course-flow__stat">{{ $t('socialnetwork.vocab.courses.courseFlowBlockStat', { block: currentBlockNumber || '—' }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@
|
|||||||
<div class="course-flow-card__top">
|
<div class="course-flow-card__top">
|
||||||
<span class="course-flow-card__badge course-flow-card__badge--review">1</span>
|
<span class="course-flow-card__badge course-flow-card__badge--review">1</span>
|
||||||
<div>
|
<div>
|
||||||
<h4>Fällige Wiederholung</h4>
|
<h4>{{ $t('socialnetwork.vocab.courses.courseFlowReviewTitle') }}</h4>
|
||||||
<p>Bereits abgeschlossene Lektionen, die heute wieder drankommen sollten.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.courseFlowReviewDescription') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="dueReviewLessons.length > 0" class="course-flow-card__list">
|
<div v-if="dueReviewLessons.length > 0" class="course-flow-card__list">
|
||||||
@@ -72,15 +72,15 @@
|
|||||||
<span>{{ formatReviewDue(getLessonProgress(lesson.id)?.reviewNextDueAt) }}</span>
|
<span>{{ formatReviewDue(getLessonProgress(lesson.id)?.reviewNextDueAt) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="course-flow-card__empty">Heute ist keine ältere Lektion als fällige Wiederholung markiert.</p>
|
<p v-else class="course-flow-card__empty">{{ $t('socialnetwork.vocab.courses.courseFlowReviewEmpty') }}</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="course-flow-card">
|
<article class="course-flow-card">
|
||||||
<div class="course-flow-card__top">
|
<div class="course-flow-card__top">
|
||||||
<span class="course-flow-card__badge course-flow-card__badge--block">2</span>
|
<span class="course-flow-card__badge course-flow-card__badge--block">2</span>
|
||||||
<div>
|
<div>
|
||||||
<h4>Aktueller Block</h4>
|
<h4>{{ $t('socialnetwork.vocab.courses.courseFlowBlockTitle') }}</h4>
|
||||||
<p>Hier liegt der nächste reguläre Fortschritt im Kurs.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.courseFlowBlockDescription') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="currentBlockLessons.length > 0" class="course-flow-card__list">
|
<div v-if="currentBlockLessons.length > 0" class="course-flow-card__list">
|
||||||
@@ -95,15 +95,15 @@
|
|||||||
<span>#{{ lesson.lessonNumber }}</span>
|
<span>#{{ lesson.lessonNumber }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="course-flow-card__empty">Der aktuelle Block ist bereits abgeschlossen oder es gibt gerade keine offene Blocklektion.</p>
|
<p v-else class="course-flow-card__empty">{{ $t('socialnetwork.vocab.courses.courseFlowBlockEmpty') }}</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="course-flow-card">
|
<article class="course-flow-card">
|
||||||
<div class="course-flow-card__top">
|
<div class="course-flow-card__top">
|
||||||
<span class="course-flow-card__badge course-flow-card__badge--intensive">3</span>
|
<span class="course-flow-card__badge course-flow-card__badge--intensive">3</span>
|
||||||
<div>
|
<div>
|
||||||
<h4>Fällige Intensivphase</h4>
|
<h4>{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveTitle') }}</h4>
|
||||||
<p>Verdichtete Wiederholung, sobald der Block davor weitgehend sitzt.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveDescription') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="nextIntensiveReviewLesson" class="course-flow-card__list">
|
<div v-if="nextIntensiveReviewLesson" class="course-flow-card__list">
|
||||||
@@ -113,18 +113,18 @@
|
|||||||
@click="openLesson(nextIntensiveReviewLesson.id)"
|
@click="openLesson(nextIntensiveReviewLesson.id)"
|
||||||
>
|
>
|
||||||
<strong>{{ nextIntensiveReviewLesson.title }}</strong>
|
<strong>{{ nextIntensiveReviewLesson.title }}</strong>
|
||||||
<span>Block {{ nextIntensiveReviewLesson.pedagogy?.blockNumber || '—' }}</span>
|
<span>{{ $t('socialnetwork.vocab.courses.lessonBlockLabel', { number: nextIntensiveReviewLesson.pedagogy?.blockNumber || '—' }) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="course-flow-card__empty">Aktuell ist keine neue Intensivphase freigeschaltet.</p>
|
<p v-else class="course-flow-card__empty">{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveEmpty') }}</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="course-flow-card">
|
<article class="course-flow-card">
|
||||||
<div class="course-flow-card__top">
|
<div class="course-flow-card__top">
|
||||||
<span class="course-flow-card__badge course-flow-card__badge--practice">4</span>
|
<span class="course-flow-card__badge course-flow-card__badge--practice">4</span>
|
||||||
<div>
|
<div>
|
||||||
<h4>Freie Vertiefung</h4>
|
<h4>{{ $t('socialnetwork.vocab.courses.courseFlowPracticeTitle') }}</h4>
|
||||||
<p>Abgeschlossene Lektionen für lockeres Nachtrainieren außerhalb des Pflichtpfads.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.courseFlowPracticeDescription') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="freePracticeLessons.length > 0" class="course-flow-card__list">
|
<div v-if="freePracticeLessons.length > 0" class="course-flow-card__list">
|
||||||
@@ -136,10 +136,10 @@
|
|||||||
@click="openLessonPractice(lesson)"
|
@click="openLessonPractice(lesson)"
|
||||||
>
|
>
|
||||||
<strong>{{ lesson.title }}</strong>
|
<strong>{{ lesson.title }}</strong>
|
||||||
<span>Im Trainer üben</span>
|
<span>{{ $t('socialnetwork.vocab.courses.practiceInTrainer') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="course-flow-card__empty">Sobald du erste Lektionen abgeschlossen hast, erscheinen sie hier für freies Nachtrainieren.</p>
|
<p v-else class="course-flow-card__empty">{{ $t('socialnetwork.vocab.courses.courseFlowPracticeEmpty') }}</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="lessons-header">
|
<div class="lessons-header">
|
||||||
<h3>{{ $t('socialnetwork.vocab.courses.lessons') }}</h3>
|
<h3>{{ $t('socialnetwork.vocab.courses.lessons') }}</h3>
|
||||||
<span class="lessons-count">{{ course.lessons.length }} Lektionen</span>
|
<span class="lessons-count">{{ $t('socialnetwork.vocab.courses.lessonsCount', { count: course.lessons.length }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="lesson-cards">
|
<div class="lesson-cards">
|
||||||
<article v-for="lesson in sortedLessons" :key="lesson.id" class="lesson-card">
|
<article v-for="lesson in sortedLessons" :key="lesson.id" class="lesson-card">
|
||||||
@@ -189,8 +189,8 @@
|
|||||||
<div class="lesson-pedagogy" v-if="lesson.pedagogy">
|
<div class="lesson-pedagogy" v-if="lesson.pedagogy">
|
||||||
<span class="lesson-chip lesson-chip--phase">{{ getPhaseLabel(lesson.pedagogy.phaseLabel) }}</span>
|
<span class="lesson-chip lesson-chip--phase">{{ getPhaseLabel(lesson.pedagogy.phaseLabel) }}</span>
|
||||||
<span class="lesson-chip lesson-chip--mode">{{ getDidacticModeLabel(lesson.pedagogy.didacticMode) }}</span>
|
<span class="lesson-chip lesson-chip--mode">{{ getDidacticModeLabel(lesson.pedagogy.didacticMode) }}</span>
|
||||||
<span v-if="lesson.pedagogy.blockNumber" class="lesson-chip lesson-chip--block">Block {{ lesson.pedagogy.blockNumber }}</span>
|
<span v-if="lesson.pedagogy.blockNumber" class="lesson-chip lesson-chip--block">{{ $t('socialnetwork.vocab.courses.lessonBlockLabel', { number: lesson.pedagogy.blockNumber }) }}</span>
|
||||||
<span v-if="lesson.pedagogy.isIntensiveReview" class="lesson-chip lesson-chip--intensive">Intensive Wiederholung</span>
|
<span v-if="lesson.pedagogy.isIntensiveReview" class="lesson-chip lesson-chip--intensive">{{ $t('socialnetwork.vocab.courses.lessonIntensiveBadge') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="lesson-actions-content">
|
<div class="lesson-actions-content">
|
||||||
<button
|
<button
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
@click="openLessonPractice(lesson)"
|
@click="openLessonPractice(lesson)"
|
||||||
class="btn-edit"
|
class="btn-edit"
|
||||||
>
|
>
|
||||||
Im Trainer üben
|
{{ $t('socialnetwork.vocab.courses.practiceInTrainer') }}
|
||||||
</button>
|
</button>
|
||||||
<button v-if="isOwner" @click="editLesson(lesson.id)" class="btn-edit">{{ $t('socialnetwork.vocab.courses.edit') }}</button>
|
<button v-if="isOwner" @click="editLesson(lesson.id)" class="btn-edit">{{ $t('socialnetwork.vocab.courses.edit') }}</button>
|
||||||
<button v-if="isOwner" @click="deleteLesson(lesson.id)" class="btn-delete">{{ $t('general.delete') }}</button>
|
<button v-if="isOwner" @click="deleteLesson(lesson.id)" class="btn-delete">{{ $t('general.delete') }}</button>
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
<option v-for="chapter in chapters" :key="chapter.id" :value="chapter.id">{{ chapter.title }}</option>
|
<option v-for="chapter in chapters" :key="chapter.id" :value="chapter.id">{{ chapter.title }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="lessonFormTouched && !canCreateLesson" class="form-error">Bitte Nummer, Titel und Kapitel vollständig angeben.</span>
|
<span v-if="lessonFormTouched && !canCreateLesson" class="form-error">{{ $t('socialnetwork.vocab.courses.addLessonValidation') }}</span>
|
||||||
<div class="form-actions form-actions-row">
|
<div class="form-actions form-actions-row">
|
||||||
<button type="submit" :disabled="!canCreateLesson">{{ $t('general.create') }}</button>
|
<button type="submit" :disabled="!canCreateLesson">{{ $t('general.create') }}</button>
|
||||||
<button type="button" @click="showAddLessonDialog = false" class="button-secondary">{{ $t('general.cancel') }}</button>
|
<button type="button" @click="showAddLessonDialog = false" class="button-secondary">{{ $t('general.cancel') }}</button>
|
||||||
@@ -460,44 +460,44 @@ export default {
|
|||||||
formatDaysSince(dateString) {
|
formatDaysSince(dateString) {
|
||||||
const days = this.daysSince(dateString);
|
const days = this.daysSince(dateString);
|
||||||
if (days <= 0) {
|
if (days <= 0) {
|
||||||
return 'heute';
|
return this.$t('socialnetwork.vocab.courses.timeToday');
|
||||||
}
|
}
|
||||||
if (days === 1) {
|
if (days === 1) {
|
||||||
return 'seit 1 Tag';
|
return this.$t('socialnetwork.vocab.courses.timeSinceOneDay');
|
||||||
}
|
}
|
||||||
return `seit ${days} Tagen`;
|
return this.$t('socialnetwork.vocab.courses.timeSinceDays', { count: days });
|
||||||
},
|
},
|
||||||
formatReviewDue(reviewNextDueAt) {
|
formatReviewDue(reviewNextDueAt) {
|
||||||
if (!reviewNextDueAt) {
|
if (!reviewNextDueAt) {
|
||||||
return 'jetzt fällig';
|
return this.$t('socialnetwork.vocab.courses.reviewDueNow');
|
||||||
}
|
}
|
||||||
const dueTimestamp = new Date(reviewNextDueAt).getTime();
|
const dueTimestamp = new Date(reviewNextDueAt).getTime();
|
||||||
if (!Number.isFinite(dueTimestamp)) {
|
if (!Number.isFinite(dueTimestamp)) {
|
||||||
return 'jetzt fällig';
|
return this.$t('socialnetwork.vocab.courses.reviewDueNow');
|
||||||
}
|
}
|
||||||
const diffMs = dueTimestamp - Date.now();
|
const diffMs = dueTimestamp - Date.now();
|
||||||
if (diffMs > 0) {
|
if (diffMs > 0) {
|
||||||
const untilDays = Math.ceil(diffMs / (24 * 60 * 60 * 1000));
|
const untilDays = Math.ceil(diffMs / (24 * 60 * 60 * 1000));
|
||||||
if (untilDays <= 1) {
|
if (untilDays <= 1) {
|
||||||
return 'morgen fällig';
|
return this.$t('socialnetwork.vocab.courses.reviewDueTomorrow');
|
||||||
}
|
}
|
||||||
return `in ${untilDays} Tagen fällig`;
|
return this.$t('socialnetwork.vocab.courses.reviewDueInDays', { count: untilDays });
|
||||||
}
|
}
|
||||||
const diffDays = Math.floor((Date.now() - dueTimestamp) / (24 * 60 * 60 * 1000));
|
const diffDays = Math.floor((Date.now() - dueTimestamp) / (24 * 60 * 60 * 1000));
|
||||||
if (diffDays <= 0) {
|
if (diffDays <= 0) {
|
||||||
return 'heute fällig';
|
return this.$t('socialnetwork.vocab.courses.reviewDueToday');
|
||||||
}
|
}
|
||||||
if (diffDays === 1) {
|
if (diffDays === 1) {
|
||||||
return 'seit 1 Tag fällig';
|
return this.$t('socialnetwork.vocab.courses.reviewDueSinceOneDay');
|
||||||
}
|
}
|
||||||
return `seit ${diffDays} Tagen fällig`;
|
return this.$t('socialnetwork.vocab.courses.reviewDueSinceDays', { count: diffDays });
|
||||||
},
|
},
|
||||||
getReviewStageLabel(progress) {
|
getReviewStageLabel(progress) {
|
||||||
const stage = Number(progress?.reviewStage || 0);
|
const stage = Number(progress?.reviewStage || 0);
|
||||||
if (stage === 0) return 'Tag 1';
|
if (stage === 0) return this.$t('socialnetwork.vocab.courses.reviewStageDay1');
|
||||||
if (stage === 1) return 'Tag 3';
|
if (stage === 1) return this.$t('socialnetwork.vocab.courses.reviewStageDay3');
|
||||||
if (stage === 2) return 'Tag 7';
|
if (stage === 2) return this.$t('socialnetwork.vocab.courses.reviewStageDay7');
|
||||||
if (stage >= 3) return 'Review abgeschlossen';
|
if (stage >= 3) return this.$t('socialnetwork.vocab.courses.reviewStageCompleted');
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
getReviewBadgeLabel(progress) {
|
getReviewBadgeLabel(progress) {
|
||||||
@@ -564,15 +564,15 @@ export default {
|
|||||||
chapterId: null
|
chapterId: null
|
||||||
};
|
};
|
||||||
await this.loadCourse();
|
await this.loadCourse();
|
||||||
showSuccess(this, 'Lektion erfolgreich angelegt.');
|
showSuccess(this, this.$t('socialnetwork.vocab.courses.addLessonSuccess'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Fehler beim Hinzufügen der Lektion:', e);
|
console.error('Fehler beim Hinzufügen der Lektion:', e);
|
||||||
showApiError(this, e, 'Fehler beim Hinzufügen der Lektion');
|
showApiError(this, e, this.$t('socialnetwork.vocab.courses.addLessonError'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async deleteLesson(lessonId) {
|
async deleteLesson(lessonId) {
|
||||||
const confirmed = await confirmAction(this, {
|
const confirmed = await confirmAction(this, {
|
||||||
title: 'Lektion löschen',
|
title: this.$t('socialnetwork.vocab.courses.deleteLessonTitle'),
|
||||||
message: this.$t('socialnetwork.vocab.courses.confirmDelete')
|
message: this.$t('socialnetwork.vocab.courses.confirmDelete')
|
||||||
});
|
});
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
@@ -581,10 +581,10 @@ export default {
|
|||||||
try {
|
try {
|
||||||
await apiClient.delete(`/api/vocab/lessons/${lessonId}`);
|
await apiClient.delete(`/api/vocab/lessons/${lessonId}`);
|
||||||
await this.loadCourse();
|
await this.loadCourse();
|
||||||
showSuccess(this, 'Lektion erfolgreich gelöscht.');
|
showSuccess(this, this.$t('socialnetwork.vocab.courses.deleteLessonSuccess'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Fehler beim Löschen der Lektion:', e);
|
console.error('Fehler beim Löschen der Lektion:', e);
|
||||||
showApiError(this, e, 'Fehler beim Löschen der Lektion');
|
showApiError(this, e, this.$t('socialnetwork.vocab.courses.deleteLessonError'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openLesson(lessonId) {
|
openLesson(lessonId) {
|
||||||
@@ -608,37 +608,37 @@ export default {
|
|||||||
getPhaseLabel(phaseLabel) {
|
getPhaseLabel(phaseLabel) {
|
||||||
switch (phaseLabel) {
|
switch (phaseLabel) {
|
||||||
case 'quickstart':
|
case 'quickstart':
|
||||||
return 'Schnellstart';
|
return this.$t('socialnetwork.vocab.courses.phaseQuickstart');
|
||||||
case 'daily_life':
|
case 'daily_life':
|
||||||
return 'Alltag';
|
return this.$t('socialnetwork.vocab.courses.phaseDailyLife');
|
||||||
case 'stabilization':
|
case 'stabilization':
|
||||||
return 'Stabilisierung';
|
return this.$t('socialnetwork.vocab.courses.phaseStabilization');
|
||||||
default:
|
default:
|
||||||
return 'Lernphase';
|
return this.$t('socialnetwork.vocab.courses.phaseDefault');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDidacticModeLabel(didacticMode) {
|
getDidacticModeLabel(didacticMode) {
|
||||||
switch (didacticMode) {
|
switch (didacticMode) {
|
||||||
case 'core_input':
|
case 'core_input':
|
||||||
return 'Neuer Stoff';
|
return this.$t('socialnetwork.vocab.courses.didacticModeCoreInput');
|
||||||
case 'guided_dialogue':
|
case 'guided_dialogue':
|
||||||
return 'Geführter Dialog';
|
return this.$t('socialnetwork.vocab.courses.didacticModeGuidedDialogue');
|
||||||
case 'contrast_training':
|
case 'contrast_training':
|
||||||
return 'Kontrasttraining';
|
return this.$t('socialnetwork.vocab.courses.didacticModeContrastTraining');
|
||||||
case 'pattern_drill':
|
case 'pattern_drill':
|
||||||
return 'Mustertraining';
|
return this.$t('socialnetwork.vocab.courses.didacticModePatternDrill');
|
||||||
case 'real_life_scenario':
|
case 'real_life_scenario':
|
||||||
return 'Alltagsszenario';
|
return this.$t('socialnetwork.vocab.courses.didacticModeRealLifeScenario');
|
||||||
case 'intensive_review':
|
case 'intensive_review':
|
||||||
return 'Wiederholungsphase';
|
return this.$t('socialnetwork.vocab.courses.didacticModeIntensiveReview');
|
||||||
case 'checkpoint':
|
case 'checkpoint':
|
||||||
return 'Checkpoint';
|
return this.$t('socialnetwork.vocab.courses.didacticModeCheckpoint');
|
||||||
default:
|
default:
|
||||||
return 'Lerneinheit';
|
return this.$t('socialnetwork.vocab.courses.didacticModeDefault');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editLesson() {
|
editLesson() {
|
||||||
showInfo(this, 'Die Bearbeitung einzelner Lektionen folgt noch.');
|
showInfo(this, this.$t('socialnetwork.vocab.courses.editLessonPending'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
<div v-if="loading">{{ $t('general.loading') }}</div>
|
<div v-if="loading">{{ $t('general.loading') }}</div>
|
||||||
<div v-else-if="!language">{{ $t('socialnetwork.vocab.notFound') }}</div>
|
<div v-else-if="!language">{{ $t('socialnetwork.vocab.notFound') }}</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<span class="vocab-language-kicker">Sprache</span>
|
<span class="vocab-language-kicker">{{ $t('socialnetwork.vocab.languageHeroEyebrow') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.vocab.languageTitle', { name: language?.name || '' }) }}</h2>
|
<h2>{{ $t('socialnetwork.vocab.languageTitle', { name: language?.name || '' }) }}</h2>
|
||||||
<p>Kapitel, Suchfunktionen und Freigaben für diese Sprache an einem Ort.</p>
|
<p>{{ $t('socialnetwork.vocab.languageHeroIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<strong>{{ getLessonTypeLabel(lesson.lessonType) }}</strong>
|
<strong>{{ getLessonTypeLabel(lesson.lessonType) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="lesson-meta-item" v-if="lessonPedagogy.didacticMode">
|
<div class="lesson-meta-item" v-if="lessonPedagogy.didacticMode">
|
||||||
<span class="lesson-meta-label">Fokus</span>
|
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonMetaFocus') }}</span>
|
||||||
<strong>{{ getDidacticModeLabel(lessonPedagogy.didacticMode) }}</strong>
|
<strong>{{ getDidacticModeLabel(lessonPedagogy.didacticMode) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="lesson-meta-item">
|
<div class="lesson-meta-item">
|
||||||
@@ -74,15 +74,15 @@
|
|||||||
</summary>
|
</summary>
|
||||||
<div class="lesson-overview-more__grid">
|
<div class="lesson-overview-more__grid">
|
||||||
<div class="lesson-meta-item" v-if="lessonPedagogy.phaseLabel">
|
<div class="lesson-meta-item" v-if="lessonPedagogy.phaseLabel">
|
||||||
<span class="lesson-meta-label">Phase</span>
|
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonMetaPhase') }}</span>
|
||||||
<strong>{{ getPhaseLabel(lessonPedagogy.phaseLabel) }}</strong>
|
<strong>{{ getPhaseLabel(lessonPedagogy.phaseLabel) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="lesson-meta-item" v-if="lessonPedagogy.newUnitTarget">
|
<div class="lesson-meta-item" v-if="lessonPedagogy.newUnitTarget">
|
||||||
<span class="lesson-meta-label">Neue Einheiten</span>
|
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonMetaNewUnits') }}</span>
|
||||||
<strong>{{ lessonPedagogy.newUnitTarget }}</strong>
|
<strong>{{ lessonPedagogy.newUnitTarget }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="lesson-meta-item" v-if="lessonPedagogy.reviewWeight != null">
|
<div class="lesson-meta-item" v-if="lessonPedagogy.reviewWeight != null">
|
||||||
<span class="lesson-meta-label">Wiederholung</span>
|
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonMetaReview') }}</span>
|
||||||
<strong>{{ lessonPedagogy.reviewWeight }}%</strong>
|
<strong>{{ lessonPedagogy.reviewWeight }}%</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,8 +90,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="lessonPedagogy.isIntensiveReview" class="lesson-intensity-banner">
|
<div v-if="lessonPedagogy.isIntensiveReview" class="lesson-intensity-banner">
|
||||||
<strong>Intensive Wiederholungsphase</strong>
|
<strong>{{ $t('socialnetwork.vocab.courses.intensiveReviewTitle') }}</strong>
|
||||||
<p>Diese Lektion priorisiert Wiederholung und Vertiefung. Neuer Stoff wird bewusst reduziert, damit vorhandene Muster stabil werden.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.intensiveReviewIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="lesson-primary-flow surface-card">
|
<section class="lesson-primary-flow surface-card">
|
||||||
@@ -173,18 +173,18 @@
|
|||||||
<div v-if="trainableLessonVocab.length > 0" class="vocab-trainer-section">
|
<div v-if="trainableLessonVocab.length > 0" class="vocab-trainer-section">
|
||||||
<h4>{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}</h4>
|
<h4>{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}</h4>
|
||||||
<div v-if="hasPreviousVocab" class="review-priority-note">
|
<div v-if="hasPreviousVocab" class="review-priority-note">
|
||||||
<strong>Wiederholung läuft schrittweise mit</strong>
|
<strong>{{ $t('socialnetwork.vocab.courses.reviewPriorityTitle') }}</strong>
|
||||||
<p>Zuerst liegt der Fokus auf den neuen Begriffen dieser Lektion. Mit deinem Fortschritt fließen ältere Vokabeln dann zunehmend mit ein.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.reviewPriorityIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasExercises && !canAccessExercises" class="exercise-lock-note">
|
<div v-if="hasExercises && !canAccessExercises" class="exercise-lock-note">
|
||||||
<strong>Kapitel-Prüfung noch gesperrt</strong>
|
<strong>{{ $t('socialnetwork.vocab.courses.exerciseLockTitle') }}</strong>
|
||||||
<p>{{ exerciseUnlockHint }}</p>
|
<p>{{ exerciseUnlockHint }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!vocabTrainerActive" class="vocab-trainer-start">
|
<div v-if="!vocabTrainerActive" class="vocab-trainer-start">
|
||||||
<template v-if="canStartVocabTrainerPrep">
|
<template v-if="canStartVocabTrainerPrep">
|
||||||
<p>{{ hasPreviousVocab ? 'Starte mit den neuen Vokabeln dieser Lektion. Mit fortschreitendem Üben mischt der Trainer automatisch passende Wiederholungen ein.' : $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}</p>
|
<p>{{ hasPreviousVocab ? $t('socialnetwork.vocab.courses.trainerStartWithReview') : $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}</p>
|
||||||
<button @click="startVocabTrainer" class="btn-start-trainer">
|
<button @click="startVocabTrainer" class="btn-start-trainer">
|
||||||
{{ hasPreviousVocab ? 'Lektion starten' : $t('socialnetwork.vocab.courses.startVocabTrainer') }}
|
{{ hasPreviousVocab ? $t('socialnetwork.vocab.courses.startLesson') : $t('socialnetwork.vocab.courses.startVocabTrainer') }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<p v-else class="vocab-trainer-locked-hint">{{ $t('socialnetwork.vocab.courses.vocabTrainerLockedHint') }}</p>
|
<p v-else class="vocab-trainer-locked-hint">{{ $t('socialnetwork.vocab.courses.vocabTrainerLockedHint') }}</p>
|
||||||
@@ -201,10 +201,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
<span class="mode-badge" :class="{ 'mode-active': vocabTrainerPhase === 'current' }">
|
<span class="mode-badge" :class="{ 'mode-active': vocabTrainerPhase === 'current' }">
|
||||||
{{ $t('socialnetwork.vocab.courses.currentLesson') || 'Aktuelle Lektion' }}
|
{{ $t('socialnetwork.vocab.courses.currentLesson') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="previousVocab && previousVocab.length > 0" class="mode-badge" :class="{ 'mode-active': vocabTrainerPhase === 'mixed' }">
|
<span v-if="previousVocab && previousVocab.length > 0" class="mode-badge" :class="{ 'mode-active': vocabTrainerPhase === 'mixed' }">
|
||||||
{{ $t('socialnetwork.vocab.courses.mixedReview') || 'Gemischt' }}
|
{{ $t('socialnetwork.vocab.courses.mixedReview') }}
|
||||||
</span>
|
</span>
|
||||||
<span class="mode-badge" :class="{ 'mode-active': vocabTrainerMode === 'multiple_choice', 'mode-completed': vocabTrainerMode === 'typing' }">
|
<span class="mode-badge" :class="{ 'mode-active': vocabTrainerMode === 'multiple_choice', 'mode-completed': vocabTrainerMode === 'typing' }">
|
||||||
{{ $t('socialnetwork.vocab.courses.modeMultipleChoice') }}
|
{{ $t('socialnetwork.vocab.courses.modeMultipleChoice') }}
|
||||||
@@ -215,9 +215,9 @@
|
|||||||
<button @click="stopVocabTrainer" class="btn-stop-trainer">{{ $t('socialnetwork.vocab.courses.stopTrainer') }}</button>
|
<button @click="stopVocabTrainer" class="btn-stop-trainer">{{ $t('socialnetwork.vocab.courses.stopTrainer') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasPreviousVocab" class="stats-row trainer-progress-row">
|
<div v-if="hasPreviousVocab" class="stats-row trainer-progress-row">
|
||||||
<span>Neue Inhalte: {{ vocabTrainerCurrentAttempts }}/{{ trainerNewFocusTarget }}</span>
|
<span>{{ $t('socialnetwork.vocab.courses.trainerProgressNewContent', { current: vocabTrainerCurrentAttempts, target: trainerNewFocusTarget }) }}</span>
|
||||||
<span>Wiederholung: {{ vocabTrainerReviewAttempts }}</span>
|
<span>{{ $t('socialnetwork.vocab.courses.trainerProgressReview', { count: vocabTrainerReviewAttempts }) }}</span>
|
||||||
<span>Mischanteil: {{ Math.round(currentReviewShare * 100) }}%</span>
|
<span>{{ $t('socialnetwork.vocab.courses.trainerProgressMixShare', { percent: Math.round(currentReviewShare * 100) }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="currentVocabQuestion" class="vocab-question">
|
<div v-if="currentVocabQuestion" class="vocab-question">
|
||||||
@@ -796,8 +796,8 @@
|
|||||||
|
|
||||||
<!-- Fallback für unbekannte Typen -->
|
<!-- Fallback für unbekannte Typen -->
|
||||||
<div v-else class="unknown-exercise">
|
<div v-else class="unknown-exercise">
|
||||||
<p>Dieser Übungstyp wird in der aktuellen Ansicht noch nicht interaktiv dargestellt.</p>
|
<p>{{ $t('socialnetwork.vocab.courses.unknownExerciseTypeNotice') }}</p>
|
||||||
<p class="unknown-exercise__type">Typ: {{ getExerciseType(exercise) }}</p>
|
<p class="unknown-exercise__type">{{ $t('socialnetwork.vocab.courses.unknownExerciseTypeLabel', { type: getExerciseType(exercise) }) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1231,12 +1231,7 @@ export default {
|
|||||||
if (!progress?.completed) {
|
if (!progress?.completed) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const stage = Number(progress.reviewStage || 0);
|
return this.getReviewStageLabel(progress);
|
||||||
if (stage === 0) return 'Tag 1';
|
|
||||||
if (stage === 1) return 'Tag 3';
|
|
||||||
if (stage === 2) return 'Tag 7';
|
|
||||||
if (stage >= 3) return 'Review abgeschlossen';
|
|
||||||
return '';
|
|
||||||
},
|
},
|
||||||
lessonReviewStatusClass() {
|
lessonReviewStatusClass() {
|
||||||
const progress = this.lessonProgress;
|
const progress = this.lessonProgress;
|
||||||
@@ -1257,12 +1252,12 @@ export default {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (progress.reviewCompleted) {
|
if (progress.reviewCompleted) {
|
||||||
return 'Diese Lektion ist in der freien Vertiefung angekommen.';
|
return this.$t('socialnetwork.vocab.courses.lessonReviewHeadlineDone');
|
||||||
}
|
}
|
||||||
if (progress.reviewDue) {
|
if (progress.reviewDue) {
|
||||||
return 'Diese Review-Welle ist jetzt fällig.';
|
return this.$t('socialnetwork.vocab.courses.lessonReviewHeadlineDue');
|
||||||
}
|
}
|
||||||
return 'Diese Lektion ist für die nächste Review-Welle vorgemerkt.';
|
return this.$t('socialnetwork.vocab.courses.lessonReviewHeadlineScheduled');
|
||||||
},
|
},
|
||||||
lessonReviewHint() {
|
lessonReviewHint() {
|
||||||
const progress = this.lessonProgress;
|
const progress = this.lessonProgress;
|
||||||
@@ -1270,9 +1265,11 @@ export default {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (progress.reviewCompleted) {
|
if (progress.reviewCompleted) {
|
||||||
return 'Die 1/3/7-Tage-Wiederholung ist abgeschlossen. Du kannst die Lektion jetzt flexibel weitertrainieren.';
|
return this.$t('socialnetwork.vocab.courses.lessonReviewHintDone');
|
||||||
}
|
}
|
||||||
return `Nächste Fälligkeit: ${this.formatLessonReviewDue(progress.reviewNextDueAt)}.`;
|
return this.$t('socialnetwork.vocab.courses.lessonReviewHintNextDue', {
|
||||||
|
due: this.formatLessonReviewDue(progress.reviewNextDueAt)
|
||||||
|
});
|
||||||
},
|
},
|
||||||
assistantAvailable() {
|
assistantAvailable() {
|
||||||
if (!this.assistantSettings) {
|
if (!this.assistantSettings) {
|
||||||
@@ -2019,35 +2016,43 @@ export default {
|
|||||||
getPhaseLabel(phaseLabel) {
|
getPhaseLabel(phaseLabel) {
|
||||||
switch (phaseLabel) {
|
switch (phaseLabel) {
|
||||||
case 'quickstart':
|
case 'quickstart':
|
||||||
return 'Schnellstart';
|
return this.$t('socialnetwork.vocab.courses.phaseQuickstart');
|
||||||
case 'daily_life':
|
case 'daily_life':
|
||||||
return 'Alltag';
|
return this.$t('socialnetwork.vocab.courses.phaseDailyLife');
|
||||||
case 'stabilization':
|
case 'stabilization':
|
||||||
return 'Stabilisierung';
|
return this.$t('socialnetwork.vocab.courses.phaseStabilization');
|
||||||
default:
|
default:
|
||||||
return 'Lernphase';
|
return this.$t('socialnetwork.vocab.courses.phaseDefault');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDidacticModeLabel(didacticMode) {
|
getDidacticModeLabel(didacticMode) {
|
||||||
switch (didacticMode) {
|
switch (didacticMode) {
|
||||||
case 'core_input':
|
case 'core_input':
|
||||||
return 'Neuer Stoff';
|
return this.$t('socialnetwork.vocab.courses.didacticModeCoreInput');
|
||||||
case 'guided_dialogue':
|
case 'guided_dialogue':
|
||||||
return 'Geführter Dialog';
|
return this.$t('socialnetwork.vocab.courses.didacticModeGuidedDialogue');
|
||||||
case 'contrast_training':
|
case 'contrast_training':
|
||||||
return 'Kontrasttraining';
|
return this.$t('socialnetwork.vocab.courses.didacticModeContrastTraining');
|
||||||
case 'pattern_drill':
|
case 'pattern_drill':
|
||||||
return 'Mustertraining';
|
return this.$t('socialnetwork.vocab.courses.didacticModePatternDrill');
|
||||||
case 'real_life_scenario':
|
case 'real_life_scenario':
|
||||||
return 'Alltagsszenario';
|
return this.$t('socialnetwork.vocab.courses.didacticModeRealLifeScenario');
|
||||||
case 'intensive_review':
|
case 'intensive_review':
|
||||||
return 'Wiederholungsphase';
|
return this.$t('socialnetwork.vocab.courses.didacticModeIntensiveReview');
|
||||||
case 'checkpoint':
|
case 'checkpoint':
|
||||||
return 'Checkpoint';
|
return this.$t('socialnetwork.vocab.courses.didacticModeCheckpoint');
|
||||||
default:
|
default:
|
||||||
return 'Lernfokus';
|
return this.$t('socialnetwork.vocab.courses.didacticModeFocusDefault');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getReviewStageLabel(progress) {
|
||||||
|
const stage = Number(progress?.reviewStage || 0);
|
||||||
|
if (stage === 0) return this.$t('socialnetwork.vocab.courses.reviewStageDay1');
|
||||||
|
if (stage === 1) return this.$t('socialnetwork.vocab.courses.reviewStageDay3');
|
||||||
|
if (stage === 2) return this.$t('socialnetwork.vocab.courses.reviewStageDay7');
|
||||||
|
if (stage >= 3) return this.$t('socialnetwork.vocab.courses.reviewStageCompleted');
|
||||||
|
return '';
|
||||||
|
},
|
||||||
formatTargetMinutes(targetMinutes) {
|
formatTargetMinutes(targetMinutes) {
|
||||||
const minutes = Number(targetMinutes);
|
const minutes = Number(targetMinutes);
|
||||||
if (!minutes) {
|
if (!minutes) {
|
||||||
@@ -2057,28 +2062,28 @@ export default {
|
|||||||
},
|
},
|
||||||
formatLessonReviewDue(reviewNextDueAt) {
|
formatLessonReviewDue(reviewNextDueAt) {
|
||||||
if (!reviewNextDueAt) {
|
if (!reviewNextDueAt) {
|
||||||
return 'jetzt';
|
return this.$t('socialnetwork.vocab.courses.reviewTimeNow');
|
||||||
}
|
}
|
||||||
const dueTimestamp = new Date(reviewNextDueAt).getTime();
|
const dueTimestamp = new Date(reviewNextDueAt).getTime();
|
||||||
if (!Number.isFinite(dueTimestamp)) {
|
if (!Number.isFinite(dueTimestamp)) {
|
||||||
return 'jetzt';
|
return this.$t('socialnetwork.vocab.courses.reviewTimeNow');
|
||||||
}
|
}
|
||||||
const diffMs = dueTimestamp - Date.now();
|
const diffMs = dueTimestamp - Date.now();
|
||||||
if (diffMs > 0) {
|
if (diffMs > 0) {
|
||||||
const untilDays = Math.ceil(diffMs / (24 * 60 * 60 * 1000));
|
const untilDays = Math.ceil(diffMs / (24 * 60 * 60 * 1000));
|
||||||
if (untilDays <= 1) {
|
if (untilDays <= 1) {
|
||||||
return 'morgen';
|
return this.$t('socialnetwork.vocab.courses.reviewTimeTomorrow');
|
||||||
}
|
}
|
||||||
return `in ${untilDays} Tagen`;
|
return this.$t('socialnetwork.vocab.courses.reviewTimeInDays', { count: untilDays });
|
||||||
}
|
}
|
||||||
const overdueDays = Math.floor((Date.now() - dueTimestamp) / (24 * 60 * 60 * 1000));
|
const overdueDays = Math.floor((Date.now() - dueTimestamp) / (24 * 60 * 60 * 1000));
|
||||||
if (overdueDays <= 0) {
|
if (overdueDays <= 0) {
|
||||||
return 'heute';
|
return this.$t('socialnetwork.vocab.courses.timeToday');
|
||||||
}
|
}
|
||||||
if (overdueDays === 1) {
|
if (overdueDays === 1) {
|
||||||
return 'seit 1 Tag';
|
return this.$t('socialnetwork.vocab.courses.timeSinceOneDay');
|
||||||
}
|
}
|
||||||
return `seit ${overdueDays} Tagen`;
|
return this.$t('socialnetwork.vocab.courses.timeSinceDays', { count: overdueDays });
|
||||||
},
|
},
|
||||||
getQuestionData(exercise) {
|
getQuestionData(exercise) {
|
||||||
if (!exercise.questionData) return null;
|
if (!exercise.questionData) return null;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vocab-new-language-view">
|
<div class="vocab-new-language-view">
|
||||||
<section class="vocab-new-language-hero surface-card">
|
<section class="vocab-new-language-hero surface-card">
|
||||||
<span class="vocab-new-language-hero__eyebrow">Vokabeltrainer</span>
|
<span class="vocab-new-language-hero__eyebrow">{{ $t('socialnetwork.vocab.newLanguageHeroEyebrow') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.vocab.newLanguageTitle') }}</h2>
|
<h2>{{ $t('socialnetwork.vocab.newLanguageTitle') }}</h2>
|
||||||
<p>Neue Sprache anlegen, Freigabecode erzeugen und direkt in die Bearbeitung wechseln.</p>
|
<p>{{ $t('socialnetwork.vocab.newLanguageHeroIntro') }}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="box surface-card">
|
<section class="box surface-card">
|
||||||
<label class="label form-field">
|
<label class="label form-field">
|
||||||
<span>{{ $t('socialnetwork.vocab.languageName') }}</span>
|
<span>{{ $t('socialnetwork.vocab.languageName') }}</span>
|
||||||
<input v-model="name" type="text" :class="{ 'field-error': nameTouched && !canSave }" />
|
<input v-model="name" type="text" :class="{ 'field-error': nameTouched && !canSave }" />
|
||||||
<span class="form-hint">Ein kurzer, klarer Sprachname reicht für den Start.</span>
|
<span class="form-hint">{{ $t('socialnetwork.vocab.newLanguageNameHint') }}</span>
|
||||||
<span v-if="nameTouched && !canSave" class="form-error">Der Name sollte mindestens 2 Zeichen haben.</span>
|
<span v-if="nameTouched && !canSave" class="form-error">{{ $t('socialnetwork.vocab.newLanguageNameValidation') }}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="actions form-actions-row">
|
<div class="actions form-actions-row">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vocab-subscribe-view">
|
<div class="vocab-subscribe-view">
|
||||||
<section class="vocab-subscribe-hero surface-card">
|
<section class="vocab-subscribe-hero surface-card">
|
||||||
<span class="vocab-subscribe-hero__eyebrow">Vokabeltrainer</span>
|
<span class="vocab-subscribe-hero__eyebrow">{{ $t('socialnetwork.vocab.subscribeHeroEyebrow') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.vocab.subscribeTitle') }}</h2>
|
<h2>{{ $t('socialnetwork.vocab.subscribeTitle') }}</h2>
|
||||||
<p>{{ $t('socialnetwork.vocab.subscribeHint') }}</p>
|
<p>{{ $t('socialnetwork.vocab.subscribeHint') }}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="vocab-view">
|
<div class="vocab-view">
|
||||||
<section class="vocab-hero surface-card">
|
<section class="vocab-hero surface-card">
|
||||||
<div>
|
<div>
|
||||||
<span class="vocab-kicker">Sprachenlernen</span>
|
<span class="vocab-kicker">{{ $t('socialnetwork.vocab.heroEyebrow') }}</span>
|
||||||
<h2>{{ $t('socialnetwork.vocab.title') }}</h2>
|
<h2>{{ $t('socialnetwork.vocab.title') }}</h2>
|
||||||
<p>{{ $t('socialnetwork.vocab.description') }}</p>
|
<p>{{ $t('socialnetwork.vocab.description') }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -14,33 +14,33 @@
|
|||||||
|
|
||||||
<section class="vocab-summary-grid">
|
<section class="vocab-summary-grid">
|
||||||
<article class="summary-card surface-card">
|
<article class="summary-card surface-card">
|
||||||
<span class="summary-card__label">Sprachen gesamt</span>
|
<span class="summary-card__label">{{ $t('socialnetwork.vocab.summaryTotalLabel') }}</span>
|
||||||
<strong>{{ languages.length }}</strong>
|
<strong>{{ languages.length }}</strong>
|
||||||
<p>Alle aktiven Sprachbereiche, in denen du Inhalte nutzt oder verwaltest.</p>
|
<p>{{ $t('socialnetwork.vocab.summaryTotalIntro') }}</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="summary-card surface-card">
|
<article class="summary-card surface-card">
|
||||||
<span class="summary-card__label">Eigene Bereiche</span>
|
<span class="summary-card__label">{{ $t('socialnetwork.vocab.summaryOwnedLabel') }}</span>
|
||||||
<strong>{{ ownedLanguages.length }}</strong>
|
<strong>{{ ownedLanguages.length }}</strong>
|
||||||
<p>Hier legst du Inhalte, Kapitel und Lernmaterial aktiv selbst an.</p>
|
<p>{{ $t('socialnetwork.vocab.summaryOwnedIntro') }}</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="summary-card surface-card">
|
<article class="summary-card surface-card">
|
||||||
<span class="summary-card__label">Abonniert</span>
|
<span class="summary-card__label">{{ $t('socialnetwork.vocab.summarySubscribedLabel') }}</span>
|
||||||
<strong>{{ subscribedLanguages.length }}</strong>
|
<strong>{{ subscribedLanguages.length }}</strong>
|
||||||
<p>Diese Bereiche sind eher für Lernen und Fortschritt statt Verwaltung gedacht.</p>
|
<p>{{ $t('socialnetwork.vocab.summarySubscribedIntro') }}</p>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="vocab-task-grid">
|
<section class="vocab-task-grid">
|
||||||
<article class="task-card surface-card">
|
<article class="task-card surface-card">
|
||||||
<span class="task-card__eyebrow">Schnellstart</span>
|
<span class="task-card__eyebrow">{{ $t('socialnetwork.vocab.taskCreateEyebrow') }}</span>
|
||||||
<h3>Neue Sprache anlegen</h3>
|
<h3>{{ $t('socialnetwork.vocab.taskCreateTitle') }}</h3>
|
||||||
<p>Der beste Einstieg, wenn du Inhalte selbst strukturieren und pflegen willst.</p>
|
<p>{{ $t('socialnetwork.vocab.taskCreateIntro') }}</p>
|
||||||
<button type="button" @click="goNewLanguage">{{ $t('socialnetwork.vocab.newLanguage') }}</button>
|
<button type="button" @click="goNewLanguage">{{ $t('socialnetwork.vocab.newLanguage') }}</button>
|
||||||
</article>
|
</article>
|
||||||
<article class="task-card surface-card">
|
<article class="task-card surface-card">
|
||||||
<span class="task-card__eyebrow">Weiterlernen</span>
|
<span class="task-card__eyebrow">{{ $t('socialnetwork.vocab.taskContinueEyebrow') }}</span>
|
||||||
<h3>Kurse und Kapitel öffnen</h3>
|
<h3>{{ $t('socialnetwork.vocab.taskContinueTitle') }}</h3>
|
||||||
<p>Springe direkt in bestehende Lernpfade und arbeite mit vorhandenen Kursen weiter.</p>
|
<p>{{ $t('socialnetwork.vocab.taskContinueIntro') }}</p>
|
||||||
<button type="button" class="button-secondary" @click="goCourses">{{ $t('socialnetwork.vocab.courses.title') }}</button>
|
<button type="button" class="button-secondary" @click="goCourses">{{ $t('socialnetwork.vocab.courses.title') }}</button>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
<section class="language-section">
|
<section class="language-section">
|
||||||
<div class="language-section__header">
|
<div class="language-section__header">
|
||||||
<div>
|
<div>
|
||||||
<h3>Eigene Sprachen</h3>
|
<h3>{{ $t('socialnetwork.vocab.ownedSectionTitle') }}</h3>
|
||||||
<p>Direkter Einstieg in Bearbeitung, Kapitel und Kursverwaltung.</p>
|
<p>{{ $t('socialnetwork.vocab.ownedSectionIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="language-section__count">{{ ownedLanguages.length }}</span>
|
<span class="language-section__count">{{ ownedLanguages.length }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,20 +64,20 @@
|
|||||||
<button type="button" class="language-card__main" @click="openLanguage(l.id)">
|
<button type="button" class="language-card__main" @click="openLanguage(l.id)">
|
||||||
<div class="language-card__info">
|
<div class="language-card__info">
|
||||||
<span class="langname">{{ l.name }}</span>
|
<span class="langname">{{ l.name }}</span>
|
||||||
<span class="language-card__hint">Verwalten und Inhalte pflegen</span>
|
<span class="language-card__hint">{{ $t('socialnetwork.vocab.ownedHint') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="role">{{ $t('socialnetwork.vocab.owner') }}</span>
|
<span class="role">{{ $t('socialnetwork.vocab.owner') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-else class="language-empty">Noch keine eigenen Sprachbereiche vorhanden.</p>
|
<p v-else class="language-empty">{{ $t('socialnetwork.vocab.ownedEmpty') }}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="language-section">
|
<section class="language-section">
|
||||||
<div class="language-section__header">
|
<div class="language-section__header">
|
||||||
<div>
|
<div>
|
||||||
<h3>Abonnierte Sprachen</h3>
|
<h3>{{ $t('socialnetwork.vocab.subscribedSectionTitle') }}</h3>
|
||||||
<p>Gut für schnellen Wiedereinstieg ins Lernen ohne Verwaltungsaufwand.</p>
|
<p>{{ $t('socialnetwork.vocab.subscribedSectionIntro') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="language-section__count">{{ subscribedLanguages.length }}</span>
|
<span class="language-section__count">{{ subscribedLanguages.length }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,13 +86,13 @@
|
|||||||
<button type="button" class="language-card__main" @click="openLanguage(l.id)">
|
<button type="button" class="language-card__main" @click="openLanguage(l.id)">
|
||||||
<div class="language-card__info">
|
<div class="language-card__info">
|
||||||
<span class="langname">{{ l.name }}</span>
|
<span class="langname">{{ l.name }}</span>
|
||||||
<span class="language-card__hint">Lernen, üben und Fortschritt ansehen</span>
|
<span class="language-card__hint">{{ $t('socialnetwork.vocab.subscribedHint') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="role">{{ $t('socialnetwork.vocab.subscribed') }}</span>
|
<span class="role">{{ $t('socialnetwork.vocab.subscribed') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-else class="language-empty">Keine abonnierten Sprachen vorhanden.</p>
|
<p v-else class="language-empty">{{ $t('socialnetwork.vocab.subscribedEmpty') }}</p>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user