feat: make salutation i18n static (#2546)
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2546 Reviewed-by: konrad <k@knt.li>
This commit is contained in:
commit
29f68747bb
@ -44,8 +44,8 @@
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
:shadow="false"
|
:shadow="false"
|
||||||
>
|
>
|
||||||
<img :src="userAvatar" alt="" class="avatar" width="40" height="40"/>
|
<img :src="authStore.avatarUrl" alt="" class="avatar" width="40" height="40"/>
|
||||||
<span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span>
|
<span class="username">{{ authStore.userDisplayName }}</span>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<icon icon="chevron-down"/>
|
<icon icon="chevron-down"/>
|
||||||
</span>
|
</span>
|
||||||
@ -80,7 +80,7 @@
|
|||||||
{{ $t('about.title') }}
|
{{ $t('about.title') }}
|
||||||
</dropdown-item>
|
</dropdown-item>
|
||||||
<dropdown-item
|
<dropdown-item
|
||||||
@click="logout()"
|
@click="authStore.logout()"
|
||||||
>
|
>
|
||||||
{{ $t('user.auth.logout') }}
|
{{ $t('user.auth.logout') }}
|
||||||
</dropdown-item>
|
</dropdown-item>
|
||||||
@ -117,8 +117,6 @@ const canWriteCurrentList = computed(() => baseStore.currentList.maxRight > Righ
|
|||||||
const menuActive = computed(() => baseStore.menuActive)
|
const menuActive = computed(() => baseStore.menuActive)
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const userInfo = computed(() => authStore.info)
|
|
||||||
const userAvatar = computed(() => authStore.avatarUrl)
|
|
||||||
|
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const imprintUrl = computed(() => configStore.legal.imprintUrl)
|
const imprintUrl = computed(() => configStore.legal.imprintUrl)
|
||||||
@ -136,10 +134,6 @@ onMounted(async () => {
|
|||||||
listTitle.value.style.setProperty('--nav-username-width', `${usernameWidth}px`)
|
listTitle.value.style.setProperty('--nav-username-width', `${usernameWidth}px`)
|
||||||
})
|
})
|
||||||
|
|
||||||
function logout() {
|
|
||||||
authStore.logout()
|
|
||||||
}
|
|
||||||
|
|
||||||
function openQuickActions() {
|
function openQuickActions() {
|
||||||
baseStore.setQuickActionsActive(true)
|
baseStore.setQuickActionsActive(true)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ import {success} from '@/message'
|
|||||||
import {useTaskStore} from '@/stores/tasks'
|
import {useTaskStore} from '@/stores/tasks'
|
||||||
|
|
||||||
import type {IUser} from '@/modelTypes/IUser'
|
import type {IUser} from '@/modelTypes/IUser'
|
||||||
|
import { getDisplayName } from '@/models/user'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
taskId: {
|
taskId: {
|
||||||
@ -65,7 +66,7 @@ const taskStore = useTaskStore()
|
|||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
const listUserService = shallowReactive(new ListUserService())
|
const listUserService = shallowReactive(new ListUserService())
|
||||||
const foundUsers = ref([])
|
const foundUsers = ref<IUser[]>([])
|
||||||
const assignees = ref<IUser[]>([])
|
const assignees = ref<IUser[]>([])
|
||||||
let isAdding = false
|
let isAdding = false
|
||||||
|
|
||||||
@ -114,13 +115,14 @@ async function findUser(query: string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await listUserService.getAll({listId: props.listId}, {s: query})
|
const response = await listUserService.getAll({listId: props.listId}, {s: query}) as IUser[]
|
||||||
|
|
||||||
// Filter the results to not include users who are already assigned
|
// Filter the results to not include users who are already assigned
|
||||||
foundUsers.value = response.filter(({id}) => !includesById(assignees.value, id))
|
foundUsers.value = response
|
||||||
|
.filter(({id}) => !includesById(assignees.value, id))
|
||||||
.map(u => {
|
.map(u => {
|
||||||
// Users may not have a display name set, so we fall back on the username in that case
|
// Users may not have a display name set, so we fall back on the username in that case
|
||||||
u.name = u.name === '' ? u.username : u.name
|
u.name = getDisplayName(u)
|
||||||
return u
|
return u
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import {describe, it, expect} from 'vitest'
|
|
||||||
import {hourToSalutation} from './useDateTimeSalutation'
|
|
||||||
|
|
||||||
const dateWithHour = (hours: number): Date => {
|
|
||||||
const date = new Date()
|
|
||||||
date.setHours(hours)
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Salutation', () => {
|
|
||||||
it('shows the right salutation in the night', () => {
|
|
||||||
const salutation = hourToSalutation(dateWithHour(4))
|
|
||||||
expect(salutation).toBe('home.welcomeNight')
|
|
||||||
})
|
|
||||||
it('shows the right salutation in the morning', () => {
|
|
||||||
const salutation = hourToSalutation(dateWithHour(8))
|
|
||||||
expect(salutation).toBe('home.welcomeMorning')
|
|
||||||
})
|
|
||||||
it('shows the right salutation in the day', () => {
|
|
||||||
const salutation = hourToSalutation(dateWithHour(13))
|
|
||||||
expect(salutation).toBe('home.welcomeDay')
|
|
||||||
})
|
|
||||||
it('shows the right salutation in the night', () => {
|
|
||||||
const salutation = hourToSalutation(dateWithHour(20))
|
|
||||||
expect(salutation).toBe('home.welcomeEvening')
|
|
||||||
})
|
|
||||||
it('shows the right salutation in the night again', () => {
|
|
||||||
const salutation = hourToSalutation(dateWithHour(23))
|
|
||||||
expect(salutation).toBe('home.welcomeNight')
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,31 +0,0 @@
|
|||||||
import {computed} from 'vue'
|
|
||||||
import {useNow} from '@vueuse/core'
|
|
||||||
|
|
||||||
const TRANSLATION_KEY_PREFIX = 'home.welcome'
|
|
||||||
|
|
||||||
export function hourToSalutation(now: Date) {
|
|
||||||
const hours = now.getHours()
|
|
||||||
|
|
||||||
if (hours < 5) {
|
|
||||||
return `${TRANSLATION_KEY_PREFIX}Night`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hours < 11) {
|
|
||||||
return `${TRANSLATION_KEY_PREFIX}Morning`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hours < 18) {
|
|
||||||
return `${TRANSLATION_KEY_PREFIX}Day`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hours < 23) {
|
|
||||||
return `${TRANSLATION_KEY_PREFIX}Evening`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${TRANSLATION_KEY_PREFIX}Night`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDateTimeSalutation() {
|
|
||||||
const now = useNow()
|
|
||||||
return computed(() => hourToSalutation(now.value))
|
|
||||||
}
|
|
26
src/composables/useDaytimeSalutation.ts
Normal file
26
src/composables/useDaytimeSalutation.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {computed} from 'vue'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
import {useNow} from '@vueuse/core'
|
||||||
|
|
||||||
|
import {useAuthStore} from '@/stores/auth'
|
||||||
|
import {hourToDaytime} from '@/helpers/hourToDaytime'
|
||||||
|
|
||||||
|
export type Daytime = 'night' | 'morning' | 'day' | 'evening'
|
||||||
|
|
||||||
|
export function useDaytimeSalutation() {
|
||||||
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
const now = useNow()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const name = computed(() => authStore.userDisplayName)
|
||||||
|
const daytime = computed(() => hourToDaytime(now.value))
|
||||||
|
|
||||||
|
const salutations = {
|
||||||
|
'night': () => t('home.welcomeNight', {username: name.value}),
|
||||||
|
'morning': () => t('home.welcomeMorning', {username: name.value}),
|
||||||
|
'day': () => t('home.welcomeDay', {username: name.value}),
|
||||||
|
'evening': () => t('home.welcomeEvening', {username: name.value}),
|
||||||
|
} as Record<Daytime, () => string>
|
||||||
|
|
||||||
|
return computed(() => name.value ? salutations[daytime.value]() : undefined)
|
||||||
|
}
|
31
src/helpers/hourToDaytime.test.ts
Normal file
31
src/helpers/hourToDaytime.test.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import {describe, it, expect} from 'vitest'
|
||||||
|
import {hourToDaytime} from "./hourToDaytime"
|
||||||
|
|
||||||
|
function dateWithHour(hours: number): Date {
|
||||||
|
const newDate = new Date()
|
||||||
|
newDate.setHours(hours, 0, 0,0 )
|
||||||
|
return newDate
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Salutation', () => {
|
||||||
|
it('shows the right salutation in the night', () => {
|
||||||
|
const salutation = hourToDaytime(dateWithHour(4))
|
||||||
|
expect(salutation).toBe('night')
|
||||||
|
})
|
||||||
|
it('shows the right salutation in the morning', () => {
|
||||||
|
const salutation = hourToDaytime(dateWithHour(8))
|
||||||
|
expect(salutation).toBe('morning')
|
||||||
|
})
|
||||||
|
it('shows the right salutation in the day', () => {
|
||||||
|
const salutation = hourToDaytime(dateWithHour(13))
|
||||||
|
expect(salutation).toBe('day')
|
||||||
|
})
|
||||||
|
it('shows the right salutation in the night', () => {
|
||||||
|
const salutation = hourToDaytime(dateWithHour(20))
|
||||||
|
expect(salutation).toBe('evening')
|
||||||
|
})
|
||||||
|
it('shows the right salutation in the night again', () => {
|
||||||
|
const salutation = hourToDaytime(dateWithHour(23))
|
||||||
|
expect(salutation).toBe('night')
|
||||||
|
})
|
||||||
|
})
|
14
src/helpers/hourToDaytime.ts
Normal file
14
src/helpers/hourToDaytime.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { Daytime } from '@/composables/useDaytimeSalutation'
|
||||||
|
|
||||||
|
export function hourToDaytime(now: Date): Daytime {
|
||||||
|
const hours = now.getHours()
|
||||||
|
|
||||||
|
const daytimeMap = {
|
||||||
|
night: hours < 5 || hours > 23,
|
||||||
|
morning: hours < 11,
|
||||||
|
day: hours < 18,
|
||||||
|
evening: hours < 23,
|
||||||
|
} as Record<Daytime, boolean>
|
||||||
|
|
||||||
|
return (Object.keys(daytimeMap) as Daytime[]).find((daytime) => daytimeMap[daytime]) || 'night'
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"welcomeNight": "Good Night {username}",
|
"welcomeNight": "Good Night {username}!",
|
||||||
"welcomeMorning": "Good Morning {username}",
|
"welcomeMorning": "Good Morning {username}!",
|
||||||
"welcomeDay": "Hi {username}",
|
"welcomeDay": "Hi {username}!",
|
||||||
"welcomeEvening": "Good Evening {username}",
|
"welcomeEvening": "Good Evening {username}!",
|
||||||
"lastViewed": "Last viewed",
|
"lastViewed": "Last viewed",
|
||||||
"list": {
|
"list": {
|
||||||
"newText": "You can create a new list for your new tasks:",
|
"newText": "You can create a new list for your new tasks:",
|
||||||
|
@ -7,7 +7,7 @@ export const AUTH_TYPES = {
|
|||||||
'LINK_SHARE': 2,
|
'LINK_SHARE': 2,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
type AuthType = typeof AUTH_TYPES[keyof typeof AUTH_TYPES]
|
export type AuthType = typeof AUTH_TYPES[keyof typeof AUTH_TYPES]
|
||||||
|
|
||||||
export interface IUser extends IAbstract {
|
export interface IUser extends IAbstract {
|
||||||
id: number
|
id: number
|
||||||
|
@ -3,7 +3,7 @@ import {defineStore, acceptHMRUpdate} from 'pinia'
|
|||||||
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
|
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
|
||||||
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
|
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
|
||||||
import {objectToSnakeCase} from '@/helpers/case'
|
import {objectToSnakeCase} from '@/helpers/case'
|
||||||
import UserModel, { getAvatarUrl } from '@/models/user'
|
import UserModel, { getAvatarUrl, getDisplayName } from '@/models/user'
|
||||||
import UserSettingsService from '@/services/userSettings'
|
import UserSettingsService from '@/services/userSettings'
|
||||||
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
|
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
|
||||||
import {setModuleLoading} from '@/stores/helper'
|
import {setModuleLoading} from '@/stores/helper'
|
||||||
@ -54,6 +54,9 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
state.info.type === AUTH_TYPES.LINK_SHARE
|
state.info.type === AUTH_TYPES.LINK_SHARE
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
userDisplayName(state) {
|
||||||
|
return state.info ? getDisplayName(state.info) : undefined
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setIsLoading(isLoading: boolean) {
|
setIsLoading(isLoading: boolean) {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content has-text-centered">
|
<div class="content has-text-centered">
|
||||||
<h2 v-if="userInfo">
|
<h2 v-if="salutation">{{ salutation }}</h2>
|
||||||
{{ $t(welcome, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
|
|
||||||
</h2>
|
|
||||||
<message variant="danger" v-if="deletionScheduledAt !== null" class="mb-4">
|
<message variant="danger" v-if="deletionScheduledAt !== null" class="mb-4">
|
||||||
{{
|
{{
|
||||||
$t('user.deletion.scheduled', {
|
$t('user.deletion.scheduled', {
|
||||||
@ -69,7 +68,7 @@ import AddTask from '@/components/tasks/add-task.vue'
|
|||||||
import {getHistory} from '@/modules/listHistory'
|
import {getHistory} from '@/modules/listHistory'
|
||||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||||
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
|
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
|
||||||
import {useDateTimeSalutation} from '@/composables/useDateTimeSalutation'
|
import {useDaytimeSalutation} from '@/composables/useDaytimeSalutation'
|
||||||
|
|
||||||
import {useBaseStore} from '@/stores/base'
|
import {useBaseStore} from '@/stores/base'
|
||||||
import {useListStore} from '@/stores/lists'
|
import {useListStore} from '@/stores/lists'
|
||||||
@ -78,7 +77,7 @@ import {useNamespaceStore} from '@/stores/namespaces'
|
|||||||
import {useAuthStore} from '@/stores/auth'
|
import {useAuthStore} from '@/stores/auth'
|
||||||
import {useTaskStore} from '@/stores/tasks'
|
import {useTaskStore} from '@/stores/tasks'
|
||||||
|
|
||||||
const welcome = useDateTimeSalutation()
|
const salutation = useDaytimeSalutation()
|
||||||
|
|
||||||
const baseStore = useBaseStore()
|
const baseStore = useBaseStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@ -99,7 +98,6 @@ const listHistory = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const migratorsEnabled = computed(() => configStore.availableMigrators?.length > 0)
|
const migratorsEnabled = computed(() => configStore.availableMigrators?.length > 0)
|
||||||
const userInfo = computed(() => authStore.info)
|
|
||||||
const hasTasks = computed(() => baseStore.hasTasks)
|
const hasTasks = computed(() => baseStore.hasTasks)
|
||||||
const defaultListId = computed(() => authStore.settings.defaultListId)
|
const defaultListId = computed(() => authStore.settings.defaultListId)
|
||||||
const defaultNamespaceId = computed(() => namespaceStore.namespaces?.[0]?.id || 0)
|
const defaultNamespaceId = computed(() => namespaceStore.namespaces?.[0]?.id || 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user