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 { ref, computed, onMounted } from 'vue'
|
||||||
import { useUserStore } from '@KTXC/stores/userStore'
|
import { useUserStore } from '@KTXC/stores/userStore'
|
||||||
import { userService } from '@KTXC/services/user/userService'
|
import { userService } from '@KTXC/services/user/userService'
|
||||||
|
import { useL10n } from '@KTXC'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const { t } = useL10n('user_profile')
|
||||||
|
|
||||||
const refInputEl = ref<HTMLElement>()
|
const refInputEl = ref<HTMLElement>()
|
||||||
const isSaving = ref(false)
|
const isSaving = ref(false)
|
||||||
@@ -134,27 +136,27 @@ const resetAvatar = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const countries = [
|
const countries = computed(() => [
|
||||||
'United States',
|
{ value: 'United States', title: t('account.countries.unitedStates', 'United States') },
|
||||||
'United Kingdom',
|
{ value: 'United Kingdom', title: t('account.countries.unitedKingdom', 'United Kingdom') },
|
||||||
'Canada',
|
{ value: 'Canada', title: t('account.countries.canada', 'Canada') },
|
||||||
'Australia',
|
{ value: 'Australia', title: t('account.countries.australia', 'Australia') },
|
||||||
'Germany',
|
{ value: 'Germany', title: t('account.countries.germany', 'Germany') },
|
||||||
'France',
|
{ value: 'France', title: t('account.countries.france', 'France') },
|
||||||
'India',
|
{ value: 'India', title: t('account.countries.india', 'India') },
|
||||||
'China',
|
{ value: 'China', title: t('account.countries.china', 'China') },
|
||||||
'Japan',
|
{ value: 'Japan', title: t('account.countries.japan', 'Japan') },
|
||||||
'Brazil',
|
{ value: 'Brazil', title: t('account.countries.brazil', 'Brazil') },
|
||||||
'Mexico',
|
{ value: 'Mexico', title: t('account.countries.mexico', 'Mexico') },
|
||||||
'South Africa',
|
{ value: 'South Africa', title: t('account.countries.southAfrica', 'South Africa') },
|
||||||
]
|
])
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VCard title="Account Details">
|
<VCard :title="t('account.title', 'Account Details')">
|
||||||
<VCardText class="d-flex">
|
<VCardText class="d-flex">
|
||||||
<!-- 👉 Avatar -->
|
<!-- 👉 Avatar -->
|
||||||
<VAvatar
|
<VAvatar
|
||||||
@@ -172,7 +174,7 @@ const countries = [
|
|||||||
@click="refInputEl?.click()"
|
@click="refInputEl?.click()"
|
||||||
>
|
>
|
||||||
<VIcon icon="mdi-cloud-upload" class="d-sm-none" />
|
<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>
|
</VBtn>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -190,13 +192,13 @@ const countries = [
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
@click="resetAvatar"
|
@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" />
|
<VIcon icon="mdi-refresh" class="d-sm-none" />
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-body-1 mb-0">
|
<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>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
@@ -214,10 +216,10 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="accountDataLocal.name_given"
|
v-model="accountDataLocal.name_given"
|
||||||
placeholder="John"
|
:placeholder="t('account.firstNamePlaceholder', 'John')"
|
||||||
label="First Name"
|
:label="t('account.firstName', 'First Name')"
|
||||||
:readonly="!isFieldEditable('name_given')"
|
: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
|
persistent-hint
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -229,10 +231,10 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="accountDataLocal.name_family"
|
v-model="accountDataLocal.name_family"
|
||||||
placeholder="Doe"
|
:placeholder="t('account.lastNamePlaceholder', 'Doe')"
|
||||||
label="Last Name"
|
:label="t('account.lastName', 'Last Name')"
|
||||||
:readonly="!isFieldEditable('name_family')"
|
: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
|
persistent-hint
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -244,11 +246,11 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="accountDataLocal.email"
|
v-model="accountDataLocal.email"
|
||||||
label="E-mail"
|
:label="t('account.email', 'E-mail')"
|
||||||
placeholder="john.doe@example.com"
|
placeholder="john.doe@example.com"
|
||||||
type="email"
|
type="email"
|
||||||
:readonly="!isFieldEditable('email')"
|
:readonly="!isFieldEditable('email')"
|
||||||
:hint="!isFieldEditable('email') ? 'Managed by authentication provider' : ''"
|
:hint="!isFieldEditable('email') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -260,10 +262,10 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="accountDataLocal.phone"
|
v-model="accountDataLocal.phone"
|
||||||
label="Phone Number"
|
:label="t('account.phoneNumber', 'Phone Number')"
|
||||||
placeholder="+1 (555) 123-4567"
|
placeholder="+1 (555) 123-4567"
|
||||||
:readonly="!isFieldEditable('phone')"
|
:readonly="!isFieldEditable('phone')"
|
||||||
:hint="!isFieldEditable('phone') ? 'Managed by authentication provider' : ''"
|
:hint="!isFieldEditable('phone') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -275,8 +277,8 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="accountDataLocal.address"
|
v-model="accountDataLocal.address"
|
||||||
label="Address"
|
:label="t('account.address', 'Address')"
|
||||||
placeholder="123 Main St"
|
:placeholder="t('account.addressPlaceholder', '123 Main St')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
@@ -287,8 +289,8 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="accountDataLocal.city"
|
v-model="accountDataLocal.city"
|
||||||
label="City"
|
:label="t('account.city', 'City')"
|
||||||
placeholder="New York"
|
:placeholder="t('account.cityPlaceholder', 'New York')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
@@ -299,8 +301,8 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="accountDataLocal.state"
|
v-model="accountDataLocal.state"
|
||||||
label="State/Province"
|
:label="t('account.stateOrProvince', 'State/Province')"
|
||||||
placeholder="State"
|
:placeholder="t('account.statePlaceholder', 'State')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
@@ -311,7 +313,7 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="accountDataLocal.zip"
|
v-model="accountDataLocal.zip"
|
||||||
label="Zip Code"
|
:label="t('account.zipCode', 'Zip Code')"
|
||||||
placeholder="10001"
|
placeholder="10001"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -323,9 +325,9 @@ const countries = [
|
|||||||
>
|
>
|
||||||
<VSelect
|
<VSelect
|
||||||
v-model="accountDataLocal.country"
|
v-model="accountDataLocal.country"
|
||||||
label="Country"
|
:label="t('account.country', 'Country')"
|
||||||
:items="countries"
|
:items="countries"
|
||||||
placeholder="Select Country"
|
:placeholder="t('account.selectCountry', 'Select Country')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
@@ -339,7 +341,7 @@ const countries = [
|
|||||||
:loading="isSaving"
|
:loading="isSaving"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
Save changes
|
{{ t('actions.saveChanges', 'Save changes') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
@@ -348,7 +350,7 @@ const countries = [
|
|||||||
type="reset"
|
type="reset"
|
||||||
@click.prevent="resetForm"
|
@click.prevent="resetForm"
|
||||||
>
|
>
|
||||||
Reset
|
{{ t('actions.reset', 'Reset') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
|
|||||||
@@ -1,69 +1,62 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useL10n } from '@KTXC'
|
||||||
|
|
||||||
const recentDevices = ref(
|
const { t } = useL10n('user_profile')
|
||||||
[
|
|
||||||
{
|
|
||||||
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 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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VCard title="Notifications">
|
<VCard :title="t('notifications.title', 'Notifications')">
|
||||||
<VCardText>
|
<VCardText>
|
||||||
We need permission from your browser to show notifications.
|
{{ t('notifications.permissionDescription', 'We need permission from your browser to show notifications.') }}
|
||||||
<a href="javascript:void(0)">Request Permission</a>
|
<a href="javascript:void(0)">{{ t('notifications.requestPermission', 'Request Permission') }}</a>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
|
|
||||||
<VTable class="text-no-wrap">
|
<VTable class="text-no-wrap">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
Type
|
{{ t('notifications.columns.type', 'Type') }}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
EMAIL
|
{{ t('notifications.columns.email', 'EMAIL') }}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
BROWSER
|
{{ t('notifications.columns.browser', 'BROWSER') }}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
App
|
{{ t('notifications.columns.app', 'App') }}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="device in recentDevices"
|
v-for="device in recentDevices"
|
||||||
:key="device.type"
|
:key="device.id"
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
{{ device.type }}
|
{{ notificationTypeLabels[device.id] }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<VCheckbox v-model="device.email" />
|
<VCheckbox v-model="device.email" />
|
||||||
@@ -82,7 +75,7 @@ const selectedNotification = ref('Only when I\'m online')
|
|||||||
<VCardText>
|
<VCardText>
|
||||||
<VForm @submit.prevent="() => {}">
|
<VForm @submit.prevent="() => {}">
|
||||||
<p class="text-base font-weight-medium">
|
<p class="text-base font-weight-medium">
|
||||||
When should we send you notifications?
|
{{ t('notifications.timingQuestion', 'When should we send you notifications?') }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<VRow>
|
<VRow>
|
||||||
@@ -93,21 +86,21 @@ const selectedNotification = ref('Only when I\'m online')
|
|||||||
<VSelect
|
<VSelect
|
||||||
v-model="selectedNotification"
|
v-model="selectedNotification"
|
||||||
mandatory
|
mandatory
|
||||||
:items="['Only when I\'m online', 'Anytime']"
|
:items="notificationTimingOptions"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||||
<VBtn type="submit">
|
<VBtn type="submit">
|
||||||
Save Changes
|
{{ t('actions.saveChanges', 'Save changes') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<VBtn
|
<VBtn
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
type="reset"
|
type="reset"
|
||||||
>
|
>
|
||||||
Reset
|
{{ t('actions.reset', 'Reset') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
</VForm>
|
</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>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
|
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
|
||||||
|
import { useL10n } from '@KTXC'
|
||||||
|
|
||||||
const integrationStore = useIntegrationStore()
|
const integrationStore = useIntegrationStore()
|
||||||
|
const { t } = useL10n('user_profile')
|
||||||
|
|
||||||
// Get all registered security panel components from modules
|
// Get all registered security panel components from modules
|
||||||
const securityPanels = computed(() => {
|
const securityPanels = computed(() => {
|
||||||
@@ -59,9 +61,11 @@ onMounted(async () => {
|
|||||||
class="mb-4"
|
class="mb-4"
|
||||||
color="grey"
|
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">
|
<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>
|
</p>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|||||||
+5
-1
@@ -5,17 +5,19 @@ const integrations: ModuleIntegrations = {
|
|||||||
{
|
{
|
||||||
id: 'user_profile',
|
id: 'user_profile',
|
||||||
label: 'Profile & Settings',
|
label: 'Profile & Settings',
|
||||||
|
l10n: 'nav.profileSettings',
|
||||||
path: '/me',
|
path: '/me',
|
||||||
icon: 'mdi-account-outline',
|
icon: 'mdi-account-outline',
|
||||||
priority: 90,
|
priority: 90,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
// User personal settings
|
// User personal settings
|
||||||
user_settings_menu: [
|
user_settings_menu: [
|
||||||
{
|
{
|
||||||
id: 'profile',
|
id: 'profile',
|
||||||
label: 'Profile',
|
label: 'Profile',
|
||||||
|
l10n: 'nav.profile',
|
||||||
path: '/me/profile',
|
path: '/me/profile',
|
||||||
icon: 'mdi-account-circle',
|
icon: 'mdi-account-circle',
|
||||||
priority: 10,
|
priority: 10,
|
||||||
@@ -23,6 +25,7 @@ const integrations: ModuleIntegrations = {
|
|||||||
{
|
{
|
||||||
id: 'preferences',
|
id: 'preferences',
|
||||||
label: 'Preferences',
|
label: 'Preferences',
|
||||||
|
l10n: 'nav.preferences',
|
||||||
path: '/me/preferences',
|
path: '/me/preferences',
|
||||||
icon: 'mdi-tune',
|
icon: 'mdi-tune',
|
||||||
priority: 20,
|
priority: 20,
|
||||||
@@ -30,6 +33,7 @@ const integrations: ModuleIntegrations = {
|
|||||||
{
|
{
|
||||||
id: 'security',
|
id: 'security',
|
||||||
label: 'Security',
|
label: 'Security',
|
||||||
|
l10n: 'nav.security',
|
||||||
path: '/me/security',
|
path: '/me/security',
|
||||||
icon: 'mdi-shield-lock',
|
icon: 'mdi-shield-lock',
|
||||||
priority: 30,
|
priority: 30,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import routes from '@/routes'
|
import routes from '@/routes'
|
||||||
import integrations from '@/integrations'
|
import integrations from '@/integrations'
|
||||||
|
|
||||||
|
// Module ships translation catalogs at static/l10n/{locale}.json
|
||||||
|
export const l10n = true
|
||||||
|
|
||||||
export { routes, integrations }
|
export { routes, integrations }
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import ProfileAccount from '@/components/ProfileAccount.vue'
|
import ProfileAccount from '@/components/ProfileAccount.vue'
|
||||||
import ProfileNotifications from '@/components/ProfileNotifications.vue'
|
import ProfileNotifications from '@/components/ProfileNotifications.vue'
|
||||||
|
import ProfilePreferences from '@/components/ProfilePreferences.vue'
|
||||||
import ProfileSecurity from '@/components/ProfileSecurity.vue'
|
import ProfileSecurity from '@/components/ProfileSecurity.vue'
|
||||||
|
import { useL10n } from '@KTXC'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const { t } = useL10n('user_profile')
|
||||||
|
|
||||||
const activeTab = ref(route.params.tab || 'account')
|
const activeTab = ref(route.params.tab || 'account')
|
||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
const tabs = [
|
const tabs = computed(() => [
|
||||||
{ title: 'Account', icon: 'mdi-account-outline', tab: 'account' },
|
{ title: t('tabs.account', 'Account'), icon: 'mdi-account-outline', tab: 'account' },
|
||||||
{ title: 'Security', icon: 'mdi-lock-outline', tab: 'security' },
|
{ title: t('tabs.security', 'Security'), icon: 'mdi-lock-outline', tab: 'security' },
|
||||||
{ title: 'Notifications', icon: 'mdi-bell-outline', tab: 'notifications' },
|
{ title: t('tabs.preferences', 'Preferences'), icon: 'mdi-tune', tab: 'preferences' },
|
||||||
]
|
{ title: t('tabs.notifications', 'Notifications'), icon: 'mdi-bell-outline', tab: 'notifications' },
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -61,6 +65,11 @@ const tabs = [
|
|||||||
<VWindowItem value="notifications">
|
<VWindowItem value="notifications">
|
||||||
<ProfileNotifications />
|
<ProfileNotifications />
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
|
|
||||||
|
<!-- Preferences -->
|
||||||
|
<VWindowItem value="preferences">
|
||||||
|
<ProfilePreferences />
|
||||||
|
</VWindowItem>
|
||||||
</VWindow>
|
</VWindow>
|
||||||
</PerfectScrollbar>
|
</PerfectScrollbar>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+26
-3
@@ -1,10 +1,30 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig, type PluginOption } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import path from 'path'
|
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/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue(), copyL10nCatalogs()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, './src'),
|
'@': path.resolve(__dirname, './src'),
|
||||||
@@ -20,7 +40,10 @@ export default defineConfig({
|
|||||||
fileName: () => 'module.mjs',
|
fileName: () => 'module.mjs',
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
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