feat: make salutation i18n static (#2546)

Reviewed-on: vikunja/frontend#2546
Reviewed-by: konrad <k@knt.li>
This commit is contained in:
konrad 2022-10-18 08:35:52 +00:00
commit 29f68747bb
11 changed files with 93 additions and 87 deletions

View File

@ -44,8 +44,8 @@
variant="secondary"
:shadow="false"
>
<img :src="userAvatar" alt="" class="avatar" width="40" height="40"/>
<span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span>
<img :src="authStore.avatarUrl" alt="" class="avatar" width="40" height="40"/>
<span class="username">{{ authStore.userDisplayName }}</span>
<span class="icon is-small">
<icon icon="chevron-down"/>
</span>
@ -80,7 +80,7 @@
{{ $t('about.title') }}
</dropdown-item>
<dropdown-item
@click="logout()"
@click="authStore.logout()"
>
{{ $t('user.auth.logout') }}
</dropdown-item>
@ -117,8 +117,6 @@ const canWriteCurrentList = computed(() => baseStore.currentList.maxRight > Righ
const menuActive = computed(() => baseStore.menuActive)
const authStore = useAuthStore()
const userInfo = computed(() => authStore.info)
const userAvatar = computed(() => authStore.avatarUrl)
const configStore = useConfigStore()
const imprintUrl = computed(() => configStore.legal.imprintUrl)
@ -136,10 +134,6 @@ onMounted(async () => {
listTitle.value.style.setProperty('--nav-username-width', `${usernameWidth}px`)
})
function logout() {
authStore.logout()
}
function openQuickActions() {
baseStore.setQuickActionsActive(true)
}

View File

@ -41,6 +41,7 @@ import {success} from '@/message'
import {useTaskStore} from '@/stores/tasks'
import type {IUser} from '@/modelTypes/IUser'
import { getDisplayName } from '@/models/user'
const props = defineProps({
taskId: {
@ -65,7 +66,7 @@ const taskStore = useTaskStore()
const {t} = useI18n({useScope: 'global'})
const listUserService = shallowReactive(new ListUserService())
const foundUsers = ref([])
const foundUsers = ref<IUser[]>([])
const assignees = ref<IUser[]>([])
let isAdding = false
@ -114,13 +115,14 @@ async function findUser(query: string) {
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
foundUsers.value = response.filter(({id}) => !includesById(assignees.value, id))
foundUsers.value = response
.filter(({id}) => !includesById(assignees.value, id))
.map(u => {
// 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
})
}

View File

@ -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')
})
})

View File

@ -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))
}

View 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)
}

View 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')
})
})

View 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'
}

View File

@ -1,9 +1,9 @@
{
"home": {
"welcomeNight": "Good Night {username}",
"welcomeMorning": "Good Morning {username}",
"welcomeDay": "Hi {username}",
"welcomeEvening": "Good Evening {username}",
"welcomeNight": "Good Night {username}!",
"welcomeMorning": "Good Morning {username}!",
"welcomeDay": "Hi {username}!",
"welcomeEvening": "Good Evening {username}!",
"lastViewed": "Last viewed",
"list": {
"newText": "You can create a new list for your new tasks:",

View File

@ -7,7 +7,7 @@ export const AUTH_TYPES = {
'LINK_SHARE': 2,
} 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 {
id: number

View File

@ -3,7 +3,7 @@ import {defineStore, acceptHMRUpdate} from 'pinia'
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
import {objectToSnakeCase} from '@/helpers/case'
import UserModel, { getAvatarUrl } from '@/models/user'
import UserModel, { getAvatarUrl, getDisplayName } from '@/models/user'
import UserSettingsService from '@/services/userSettings'
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
import {setModuleLoading} from '@/stores/helper'
@ -54,6 +54,9 @@ export const useAuthStore = defineStore('auth', {
state.info.type === AUTH_TYPES.LINK_SHARE
)
},
userDisplayName(state) {
return state.info ? getDisplayName(state.info) : undefined
},
},
actions: {
setIsLoading(isLoading: boolean) {

View File

@ -1,8 +1,7 @@
<template>
<div class="content has-text-centered">
<h2 v-if="userInfo">
{{ $t(welcome, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
</h2>
<h2 v-if="salutation">{{ salutation }}</h2>
<message variant="danger" v-if="deletionScheduledAt !== null" class="mb-4">
{{
$t('user.deletion.scheduled', {
@ -69,7 +68,7 @@ import AddTask from '@/components/tasks/add-task.vue'
import {getHistory} from '@/modules/listHistory'
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
import {useDateTimeSalutation} from '@/composables/useDateTimeSalutation'
import {useDaytimeSalutation} from '@/composables/useDaytimeSalutation'
import {useBaseStore} from '@/stores/base'
import {useListStore} from '@/stores/lists'
@ -78,7 +77,7 @@ import {useNamespaceStore} from '@/stores/namespaces'
import {useAuthStore} from '@/stores/auth'
import {useTaskStore} from '@/stores/tasks'
const welcome = useDateTimeSalutation()
const salutation = useDaytimeSalutation()
const baseStore = useBaseStore()
const authStore = useAuthStore()
@ -99,7 +98,6 @@ const listHistory = computed(() => {
})
const migratorsEnabled = computed(() => configStore.availableMigrators?.length > 0)
const userInfo = computed(() => authStore.info)
const hasTasks = computed(() => baseStore.hasTasks)
const defaultListId = computed(() => authStore.settings.defaultListId)
const defaultNamespaceId = computed(() => namespaceStore.namespaces?.[0]?.id || 0)