feat: localizations
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"account": {
|
||||
"address": "Adresse",
|
||||
"addressPlaceholder": "Musterstraße 123",
|
||||
"city": "Stadt",
|
||||
"cityPlaceholder": "Berlin",
|
||||
"countries": {
|
||||
"australia": "Australien",
|
||||
"brazil": "Brasilien",
|
||||
"canada": "Kanada",
|
||||
"china": "China",
|
||||
"france": "Frankreich",
|
||||
"germany": "Deutschland",
|
||||
"india": "Indien",
|
||||
"japan": "Japan",
|
||||
"mexico": "Mexiko",
|
||||
"southAfrica": "Südafrika",
|
||||
"unitedKingdom": "Vereinigtes Königreich",
|
||||
"unitedStates": "Vereinigte Staaten"
|
||||
},
|
||||
"country": "Land",
|
||||
"email": "E-Mail",
|
||||
"firstName": "Vorname",
|
||||
"firstNamePlaceholder": "Max",
|
||||
"lastName": "Nachname",
|
||||
"lastNamePlaceholder": "Mustermann",
|
||||
"managedFieldHint": "Wird vom Authentifizierungsanbieter verwaltet",
|
||||
"phoneNumber": "Telefonnummer",
|
||||
"photoRequirements": "Zulässig sind JPG, GIF oder PNG. Maximale Größe: 800 KB",
|
||||
"selectCountry": "Land auswählen",
|
||||
"stateOrProvince": "Bundesland/Provinz",
|
||||
"statePlaceholder": "Bundesland",
|
||||
"title": "Kontodetails",
|
||||
"uploadPhoto": "Neues Foto hochladen",
|
||||
"zipCode": "Postleitzahl"
|
||||
},
|
||||
"actions": {
|
||||
"reset": "Zurücksetzen",
|
||||
"saveChanges": "Änderungen speichern"
|
||||
},
|
||||
"nav": {
|
||||
"preferences": "Einstellungen",
|
||||
"profile": "Profil",
|
||||
"profileSettings": "Profil & Einstellungen",
|
||||
"security": "Sicherheit"
|
||||
},
|
||||
"notifications": {
|
||||
"columns": {
|
||||
"app": "App",
|
||||
"browser": "BROWSER",
|
||||
"email": "E-MAIL",
|
||||
"type": "Typ"
|
||||
},
|
||||
"permissionDescription": "Wir benötigen die Berechtigung Ihres Browsers, um Benachrichtigungen anzuzeigen.",
|
||||
"requestPermission": "Berechtigung anfordern",
|
||||
"timing": {
|
||||
"anytime": "Jederzeit",
|
||||
"online": "Nur wenn ich online bin"
|
||||
},
|
||||
"timingQuestion": "Wann sollen wir Ihnen Benachrichtigungen senden?",
|
||||
"title": "Benachrichtigungen",
|
||||
"types": {
|
||||
"accountActivity": "Kontoaktivität",
|
||||
"newBrowserSignIn": "Anmeldung über einen neuen Browser",
|
||||
"newDeviceLinked": "Ein neues Gerät wurde verknüpft",
|
||||
"newForYou": "Neu für Sie"
|
||||
}
|
||||
},
|
||||
"preferences": {
|
||||
"languageHint": "Wird sofort angewendet und in Ihrem Konto gespeichert",
|
||||
"languageLabel": "Anzeigesprache",
|
||||
"languageTitle": "Sprache"
|
||||
},
|
||||
"security": {
|
||||
"emptyDescription": "Derzeit sind keine Sicherheitsmodule aktiviert oder installiert.",
|
||||
"emptyTitle": "Keine Sicherheitseinstellungen verfügbar"
|
||||
},
|
||||
"tabs": {
|
||||
"account": "Konto",
|
||||
"notifications": "Benachrichtigungen",
|
||||
"preferences": "Einstellungen",
|
||||
"security": "Sicherheit"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"account": {
|
||||
"address": "Address",
|
||||
"addressPlaceholder": "123 Main St",
|
||||
"city": "City",
|
||||
"cityPlaceholder": "New York",
|
||||
"countries": {
|
||||
"australia": "Australia",
|
||||
"brazil": "Brazil",
|
||||
"canada": "Canada",
|
||||
"china": "China",
|
||||
"france": "France",
|
||||
"germany": "Germany",
|
||||
"india": "India",
|
||||
"japan": "Japan",
|
||||
"mexico": "Mexico",
|
||||
"southAfrica": "South Africa",
|
||||
"unitedKingdom": "United Kingdom",
|
||||
"unitedStates": "United States"
|
||||
},
|
||||
"country": "Country",
|
||||
"email": "E-mail",
|
||||
"firstName": "First Name",
|
||||
"firstNamePlaceholder": "John",
|
||||
"lastName": "Last Name",
|
||||
"lastNamePlaceholder": "Doe",
|
||||
"managedFieldHint": "Managed by authentication provider",
|
||||
"phoneNumber": "Phone Number",
|
||||
"photoRequirements": "Allowed JPG, GIF or PNG. Max size of 800K",
|
||||
"selectCountry": "Select Country",
|
||||
"stateOrProvince": "State/Province",
|
||||
"statePlaceholder": "State",
|
||||
"title": "Account Details",
|
||||
"uploadPhoto": "Upload new photo",
|
||||
"zipCode": "Zip Code"
|
||||
},
|
||||
"actions": {
|
||||
"reset": "Reset",
|
||||
"saveChanges": "Save changes"
|
||||
},
|
||||
"nav": {
|
||||
"preferences": "Preferences",
|
||||
"profile": "Profile",
|
||||
"profileSettings": "Profile & Settings",
|
||||
"security": "Security"
|
||||
},
|
||||
"notifications": {
|
||||
"columns": {
|
||||
"app": "App",
|
||||
"browser": "BROWSER",
|
||||
"email": "EMAIL",
|
||||
"type": "Type"
|
||||
},
|
||||
"permissionDescription": "We need permission from your browser to show notifications.",
|
||||
"requestPermission": "Request Permission",
|
||||
"timing": {
|
||||
"anytime": "Anytime",
|
||||
"online": "Only when I'm online"
|
||||
},
|
||||
"timingQuestion": "When should we send you notifications?",
|
||||
"title": "Notifications",
|
||||
"types": {
|
||||
"accountActivity": "Account activity",
|
||||
"newBrowserSignIn": "A new browser used to sign in",
|
||||
"newDeviceLinked": "A new device is linked",
|
||||
"newForYou": "New for you"
|
||||
}
|
||||
},
|
||||
"preferences": {
|
||||
"languageHint": "Applies immediately and is saved to your account",
|
||||
"languageLabel": "Display language",
|
||||
"languageTitle": "Language"
|
||||
},
|
||||
"security": {
|
||||
"emptyDescription": "No security modules are currently enabled or installed.",
|
||||
"emptyTitle": "No Security Settings Available"
|
||||
},
|
||||
"tabs": {
|
||||
"account": "Account",
|
||||
"notifications": "Notifications",
|
||||
"preferences": "Preferences",
|
||||
"security": "Security"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"nav": {
|
||||
"preferences": "Preferences",
|
||||
"profile": "Profile",
|
||||
"profileSettings": "Profile & Settings",
|
||||
"security": "Security"
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useUserStore } from '@KTXC/stores/userStore'
|
||||
import { userService } from '@KTXC/services/user/userService'
|
||||
import { useL10n } from '@KTXC'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { t } = useL10n('user_profile')
|
||||
|
||||
const refInputEl = ref<HTMLElement>()
|
||||
const isSaving = ref(false)
|
||||
@@ -134,27 +136,27 @@ const resetAvatar = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const countries = [
|
||||
'United States',
|
||||
'United Kingdom',
|
||||
'Canada',
|
||||
'Australia',
|
||||
'Germany',
|
||||
'France',
|
||||
'India',
|
||||
'China',
|
||||
'Japan',
|
||||
'Brazil',
|
||||
'Mexico',
|
||||
'South Africa',
|
||||
]
|
||||
const countries = computed(() => [
|
||||
{ value: 'United States', title: t('account.countries.unitedStates', 'United States') },
|
||||
{ value: 'United Kingdom', title: t('account.countries.unitedKingdom', 'United Kingdom') },
|
||||
{ value: 'Canada', title: t('account.countries.canada', 'Canada') },
|
||||
{ value: 'Australia', title: t('account.countries.australia', 'Australia') },
|
||||
{ value: 'Germany', title: t('account.countries.germany', 'Germany') },
|
||||
{ value: 'France', title: t('account.countries.france', 'France') },
|
||||
{ value: 'India', title: t('account.countries.india', 'India') },
|
||||
{ value: 'China', title: t('account.countries.china', 'China') },
|
||||
{ value: 'Japan', title: t('account.countries.japan', 'Japan') },
|
||||
{ value: 'Brazil', title: t('account.countries.brazil', 'Brazil') },
|
||||
{ value: 'Mexico', title: t('account.countries.mexico', 'Mexico') },
|
||||
{ value: 'South Africa', title: t('account.countries.southAfrica', 'South Africa') },
|
||||
])
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard title="Account Details">
|
||||
<VCard :title="t('account.title', 'Account Details')">
|
||||
<VCardText class="d-flex">
|
||||
<!-- 👉 Avatar -->
|
||||
<VAvatar
|
||||
@@ -172,7 +174,7 @@ const countries = [
|
||||
@click="refInputEl?.click()"
|
||||
>
|
||||
<VIcon icon="mdi-cloud-upload" class="d-sm-none" />
|
||||
<span class="d-none d-sm-block">Upload new photo</span>
|
||||
<span class="d-none d-sm-block">{{ t('account.uploadPhoto', 'Upload new photo') }}</span>
|
||||
</VBtn>
|
||||
|
||||
<input
|
||||
@@ -190,13 +192,13 @@ const countries = [
|
||||
variant="outlined"
|
||||
@click="resetAvatar"
|
||||
>
|
||||
<span class="d-none d-sm-block">Reset</span>
|
||||
<span class="d-none d-sm-block">{{ t('actions.reset', 'Reset') }}</span>
|
||||
<VIcon icon="mdi-refresh" class="d-sm-none" />
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<p class="text-body-1 mb-0">
|
||||
Allowed JPG, GIF or PNG. Max size of 800K
|
||||
{{ t('account.photoRequirements', 'Allowed JPG, GIF or PNG. Max size of 800K') }}
|
||||
</p>
|
||||
</form>
|
||||
</VCardText>
|
||||
@@ -214,10 +216,10 @@ const countries = [
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountDataLocal.name_given"
|
||||
placeholder="John"
|
||||
label="First Name"
|
||||
:placeholder="t('account.firstNamePlaceholder', 'John')"
|
||||
:label="t('account.firstName', 'First Name')"
|
||||
:readonly="!isFieldEditable('name_given')"
|
||||
:hint="!isFieldEditable('name_given') ? 'Managed by authentication provider' : ''"
|
||||
:hint="!isFieldEditable('name_given') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
@@ -229,10 +231,10 @@ const countries = [
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountDataLocal.name_family"
|
||||
placeholder="Doe"
|
||||
label="Last Name"
|
||||
:placeholder="t('account.lastNamePlaceholder', 'Doe')"
|
||||
:label="t('account.lastName', 'Last Name')"
|
||||
:readonly="!isFieldEditable('name_family')"
|
||||
:hint="!isFieldEditable('name_family') ? 'Managed by authentication provider' : ''"
|
||||
:hint="!isFieldEditable('name_family') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
@@ -244,11 +246,11 @@ const countries = [
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountDataLocal.email"
|
||||
label="E-mail"
|
||||
:label="t('account.email', 'E-mail')"
|
||||
placeholder="john.doe@example.com"
|
||||
type="email"
|
||||
:readonly="!isFieldEditable('email')"
|
||||
:hint="!isFieldEditable('email') ? 'Managed by authentication provider' : ''"
|
||||
:hint="!isFieldEditable('email') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
@@ -260,10 +262,10 @@ const countries = [
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountDataLocal.phone"
|
||||
label="Phone Number"
|
||||
:label="t('account.phoneNumber', 'Phone Number')"
|
||||
placeholder="+1 (555) 123-4567"
|
||||
:readonly="!isFieldEditable('phone')"
|
||||
:hint="!isFieldEditable('phone') ? 'Managed by authentication provider' : ''"
|
||||
:hint="!isFieldEditable('phone') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
@@ -275,8 +277,8 @@ const countries = [
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountDataLocal.address"
|
||||
label="Address"
|
||||
placeholder="123 Main St"
|
||||
:label="t('account.address', 'Address')"
|
||||
:placeholder="t('account.addressPlaceholder', '123 Main St')"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
@@ -287,8 +289,8 @@ const countries = [
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountDataLocal.city"
|
||||
label="City"
|
||||
placeholder="New York"
|
||||
:label="t('account.city', 'City')"
|
||||
:placeholder="t('account.cityPlaceholder', 'New York')"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
@@ -299,8 +301,8 @@ const countries = [
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountDataLocal.state"
|
||||
label="State/Province"
|
||||
placeholder="State"
|
||||
:label="t('account.stateOrProvince', 'State/Province')"
|
||||
:placeholder="t('account.statePlaceholder', 'State')"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
@@ -311,7 +313,7 @@ const countries = [
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountDataLocal.zip"
|
||||
label="Zip Code"
|
||||
:label="t('account.zipCode', 'Zip Code')"
|
||||
placeholder="10001"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -323,9 +325,9 @@ const countries = [
|
||||
>
|
||||
<VSelect
|
||||
v-model="accountDataLocal.country"
|
||||
label="Country"
|
||||
:label="t('account.country', 'Country')"
|
||||
:items="countries"
|
||||
placeholder="Select Country"
|
||||
:placeholder="t('account.selectCountry', 'Select Country')"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
@@ -339,7 +341,7 @@ const countries = [
|
||||
:loading="isSaving"
|
||||
color="primary"
|
||||
>
|
||||
Save changes
|
||||
{{ t('actions.saveChanges', 'Save changes') }}
|
||||
</VBtn>
|
||||
|
||||
<VBtn
|
||||
@@ -348,7 +350,7 @@ const countries = [
|
||||
type="reset"
|
||||
@click.prevent="resetForm"
|
||||
>
|
||||
Reset
|
||||
{{ t('actions.reset', 'Reset') }}
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
@@ -1,69 +1,62 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useL10n } from '@KTXC'
|
||||
|
||||
const recentDevices = ref(
|
||||
[
|
||||
{
|
||||
type: 'New for you',
|
||||
email: true,
|
||||
browser: true,
|
||||
app: true,
|
||||
},
|
||||
{
|
||||
type: 'Account activity',
|
||||
email: true,
|
||||
browser: true,
|
||||
app: true,
|
||||
},
|
||||
{
|
||||
type: 'A new browser used to sign in',
|
||||
email: true,
|
||||
browser: true,
|
||||
app: false,
|
||||
},
|
||||
{
|
||||
type: 'A new device is linked',
|
||||
email: true,
|
||||
browser: false,
|
||||
app: false,
|
||||
},
|
||||
],
|
||||
)
|
||||
const { t } = useL10n('user_profile')
|
||||
|
||||
const selectedNotification = ref('Only when I\'m online')
|
||||
const recentDevices = ref([
|
||||
{ id: 'newForYou', email: true, browser: true, app: true },
|
||||
{ id: 'accountActivity', email: true, browser: true, app: true },
|
||||
{ id: 'newBrowserSignIn', email: true, browser: true, app: false },
|
||||
{ id: 'newDeviceLinked', email: true, browser: false, app: false },
|
||||
])
|
||||
|
||||
const notificationTypeLabels = computed<Record<string, string>>(() => ({
|
||||
newForYou: t('notifications.types.newForYou', 'New for you'),
|
||||
accountActivity: t('notifications.types.accountActivity', 'Account activity'),
|
||||
newBrowserSignIn: t('notifications.types.newBrowserSignIn', 'A new browser used to sign in'),
|
||||
newDeviceLinked: t('notifications.types.newDeviceLinked', 'A new device is linked'),
|
||||
}))
|
||||
|
||||
const notificationTimingOptions = computed(() => [
|
||||
{ value: 'online', title: t('notifications.timing.online', 'Only when I\'m online') },
|
||||
{ value: 'anytime', title: t('notifications.timing.anytime', 'Anytime') },
|
||||
])
|
||||
|
||||
const selectedNotification = ref('online')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard title="Notifications">
|
||||
<VCard :title="t('notifications.title', 'Notifications')">
|
||||
<VCardText>
|
||||
We need permission from your browser to show notifications.
|
||||
<a href="javascript:void(0)">Request Permission</a>
|
||||
{{ t('notifications.permissionDescription', 'We need permission from your browser to show notifications.') }}
|
||||
<a href="javascript:void(0)">{{ t('notifications.requestPermission', 'Request Permission') }}</a>
|
||||
</VCardText>
|
||||
|
||||
<VTable class="text-no-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
Type
|
||||
{{ t('notifications.columns.type', 'Type') }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
EMAIL
|
||||
{{ t('notifications.columns.email', 'EMAIL') }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
BROWSER
|
||||
{{ t('notifications.columns.browser', 'BROWSER') }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
App
|
||||
{{ t('notifications.columns.app', 'App') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="device in recentDevices"
|
||||
:key="device.type"
|
||||
:key="device.id"
|
||||
>
|
||||
<td>
|
||||
{{ device.type }}
|
||||
{{ notificationTypeLabels[device.id] }}
|
||||
</td>
|
||||
<td>
|
||||
<VCheckbox v-model="device.email" />
|
||||
@@ -82,7 +75,7 @@ const selectedNotification = ref('Only when I\'m online')
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<p class="text-base font-weight-medium">
|
||||
When should we send you notifications?
|
||||
{{ t('notifications.timingQuestion', 'When should we send you notifications?') }}
|
||||
</p>
|
||||
|
||||
<VRow>
|
||||
@@ -93,21 +86,21 @@ const selectedNotification = ref('Only when I\'m online')
|
||||
<VSelect
|
||||
v-model="selectedNotification"
|
||||
mandatory
|
||||
:items="['Only when I\'m online', 'Anytime']"
|
||||
:items="notificationTimingOptions"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit">
|
||||
Save Changes
|
||||
{{ t('actions.saveChanges', 'Save changes') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
type="reset"
|
||||
>
|
||||
Reset
|
||||
{{ t('actions.reset', 'Reset') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useL10nStore, useL10n } from '@KTXC'
|
||||
|
||||
const l10nStore = useL10nStore()
|
||||
const { t } = useL10n('user_profile')
|
||||
|
||||
// Offer each available locale under its own native name
|
||||
const languageOptions = computed(() =>
|
||||
l10nStore.available.map((locale: string) => {
|
||||
let title = locale
|
||||
try {
|
||||
title = new Intl.DisplayNames([locale], { type: 'language' }).of(locale) ?? locale
|
||||
} catch {
|
||||
// unknown tag: fall back to the raw locale code
|
||||
}
|
||||
return { value: locale, title }
|
||||
})
|
||||
)
|
||||
|
||||
const selectLanguage = (locale: string | null) => {
|
||||
if (locale && locale !== l10nStore.locale) {
|
||||
l10nStore.setLocale(locale)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VCard :title="t('preferences.languageTitle', 'Language')">
|
||||
<VCardText>
|
||||
<VSelect
|
||||
:model-value="l10nStore.locale"
|
||||
:items="languageOptions"
|
||||
:label="t('preferences.languageLabel', 'Display language')"
|
||||
:hint="t('preferences.languageHint', 'Applies immediately and is saved to your account')"
|
||||
persistent-hint
|
||||
@update:model-value="selectLanguage"
|
||||
/>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
|
||||
import { useL10n } from '@KTXC'
|
||||
|
||||
const integrationStore = useIntegrationStore()
|
||||
const { t } = useL10n('user_profile')
|
||||
|
||||
// Get all registered security panel components from modules
|
||||
const securityPanels = computed(() => {
|
||||
@@ -59,9 +61,11 @@ onMounted(async () => {
|
||||
class="mb-4"
|
||||
color="grey"
|
||||
/>
|
||||
<p class="text-h6 mb-2">No Security Settings Available</p>
|
||||
<p class="text-h6 mb-2">
|
||||
{{ t('security.emptyTitle', 'No Security Settings Available') }}
|
||||
</p>
|
||||
<p class="text-body-2 text-medium-emphasis">
|
||||
No security modules are currently enabled or installed.
|
||||
{{ t('security.emptyDescription', 'No security modules are currently enabled or installed.') }}
|
||||
</p>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
+5
-1
@@ -5,17 +5,19 @@ const integrations: ModuleIntegrations = {
|
||||
{
|
||||
id: 'user_profile',
|
||||
label: 'Profile & Settings',
|
||||
l10n: 'nav.profileSettings',
|
||||
path: '/me',
|
||||
icon: 'mdi-account-outline',
|
||||
priority: 90,
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
// User personal settings
|
||||
user_settings_menu: [
|
||||
{
|
||||
id: 'profile',
|
||||
label: 'Profile',
|
||||
l10n: 'nav.profile',
|
||||
path: '/me/profile',
|
||||
icon: 'mdi-account-circle',
|
||||
priority: 10,
|
||||
@@ -23,6 +25,7 @@ const integrations: ModuleIntegrations = {
|
||||
{
|
||||
id: 'preferences',
|
||||
label: 'Preferences',
|
||||
l10n: 'nav.preferences',
|
||||
path: '/me/preferences',
|
||||
icon: 'mdi-tune',
|
||||
priority: 20,
|
||||
@@ -30,6 +33,7 @@ const integrations: ModuleIntegrations = {
|
||||
{
|
||||
id: 'security',
|
||||
label: 'Security',
|
||||
l10n: 'nav.security',
|
||||
path: '/me/security',
|
||||
icon: 'mdi-shield-lock',
|
||||
priority: 30,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import routes from '@/routes'
|
||||
import integrations from '@/integrations'
|
||||
|
||||
// Module ships translation catalogs at static/l10n/{locale}.json
|
||||
export const l10n = true
|
||||
|
||||
export { routes, integrations }
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import ProfileAccount from '@/components/ProfileAccount.vue'
|
||||
import ProfileNotifications from '@/components/ProfileNotifications.vue'
|
||||
import ProfilePreferences from '@/components/ProfilePreferences.vue'
|
||||
import ProfileSecurity from '@/components/ProfileSecurity.vue'
|
||||
import { useL10n } from '@KTXC'
|
||||
|
||||
const route = useRoute()
|
||||
const { t } = useL10n('user_profile')
|
||||
|
||||
const activeTab = ref(route.params.tab || 'account')
|
||||
|
||||
// tabs
|
||||
const tabs = [
|
||||
{ title: 'Account', icon: 'mdi-account-outline', tab: 'account' },
|
||||
{ title: 'Security', icon: 'mdi-lock-outline', tab: 'security' },
|
||||
{ title: 'Notifications', icon: 'mdi-bell-outline', tab: 'notifications' },
|
||||
]
|
||||
const tabs = computed(() => [
|
||||
{ title: t('tabs.account', 'Account'), icon: 'mdi-account-outline', tab: 'account' },
|
||||
{ title: t('tabs.security', 'Security'), icon: 'mdi-lock-outline', tab: 'security' },
|
||||
{ title: t('tabs.preferences', 'Preferences'), icon: 'mdi-tune', tab: 'preferences' },
|
||||
{ title: t('tabs.notifications', 'Notifications'), icon: 'mdi-bell-outline', tab: 'notifications' },
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -61,6 +65,11 @@ const tabs = [
|
||||
<VWindowItem value="notifications">
|
||||
<ProfileNotifications />
|
||||
</VWindowItem>
|
||||
|
||||
<!-- Preferences -->
|
||||
<VWindowItem value="preferences">
|
||||
<ProfilePreferences />
|
||||
</VWindowItem>
|
||||
</VWindow>
|
||||
</PerfectScrollbar>
|
||||
</template>
|
||||
|
||||
+26
-3
@@ -1,10 +1,30 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, type PluginOption } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
|
||||
// Copy translation catalogs (l10n/*.json, except the extractor-only
|
||||
// en.manual.json) into static/l10n/ so the core shell can fetch them at
|
||||
// /modules/{handle}/static/l10n/{locale}.json
|
||||
const copyL10nCatalogs = (): PluginOption => ({
|
||||
name: 'copy-l10n-catalogs',
|
||||
apply: 'build',
|
||||
closeBundle() {
|
||||
const sourceDir = path.resolve(__dirname, 'l10n')
|
||||
const outDir = path.resolve(__dirname, 'static/l10n')
|
||||
if (!fs.existsSync(sourceDir)) return
|
||||
fs.mkdirSync(outDir, { recursive: true })
|
||||
for (const file of fs.readdirSync(sourceDir)) {
|
||||
if (file.endsWith('.json') && !file.endsWith('.manual.json')) {
|
||||
fs.copyFileSync(path.join(sourceDir, file), path.join(outDir, file))
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
plugins: [vue(), copyL10nCatalogs()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
@@ -20,7 +40,10 @@ export default defineConfig({
|
||||
fileName: () => 'module.mjs',
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['vue', 'vue-router', 'pinia'],
|
||||
external: ['vue', 'vue-router', 'pinia', '@KTXC'],
|
||||
output: {
|
||||
paths: (id) => (id === '@KTXC' ? '/js/ktxc.mjs' : id),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user