1
0
mirror of https://github.com/elk-zone/elk synced 2024-11-27 06:18:07 +09:00

feat: bump to latest vue 3.4.19 (#2607)

Co-authored-by: patak <matias.capeletto@gmail.com>
This commit is contained in:
Joaquín Sánchez 2024-02-24 13:24:21 +01:00 committed by GitHub
parent 81ef8ff9aa
commit 36004a7eba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 601 additions and 451 deletions

View File

@ -1,5 +1,8 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
import { fetchAccountByHandle } from '~/composables/cache'
type WatcherType = [acc?: mastodon.v1.Account, h?: string, v?: boolean]
defineOptions({
inheritAttrs: false,
@ -11,16 +14,55 @@ const props = defineProps<{
disabled?: boolean
}>()
const account = computed(() => props.account || (props.handle ? useAccountByHandle(props.handle!) : undefined))
const hoverCard = ref()
const targetIsVisible = ref(false)
const account = ref<mastodon.v1.Account | null | undefined>(props.account)
useIntersectionObserver(
hoverCard,
([{ intersectionRatio }]) => {
targetIsVisible.value = intersectionRatio <= 0.75
},
)
watch(
() => [props.account, props.handle, targetIsVisible.value] satisfies WatcherType,
([newAccount, newHandle, newVisible], oldProps) => {
if (newAccount) {
account.value = newAccount
return
}
if (!newVisible)
return
if (newHandle) {
const [_oldAccount, oldHandle, _oldVisible] = oldProps ?? [undefined, undefined, false]
if (!oldHandle || newHandle !== oldHandle || !account.value) {
// new handle can be wrong: using server instead of webDomain
fetchAccountByHandle(newHandle).then((acc) => {
if (newHandle === props.handle)
account.value = acc
})
}
return
}
account.value = undefined
}, { immediate: true, flush: 'post' },
)
const userSettings = useUserSettings()
</script>
<template>
<VMenu v-if="!disabled && account && !getPreferences(userSettings, 'hideAccountHoverCard')" placement="bottom-start" :delay="{ show: 500, hide: 100 }" v-bind="$attrs" :close-on-content-click="false">
<slot />
<template #popper>
<AccountHoverCard v-if="account" :account="account" />
</template>
</VMenu>
<slot v-else />
<span ref="hoverCard">
<VMenu v-if="!disabled && account && !getPreferences(userSettings, 'hideAccountHoverCard')" placement="bottom-start" :delay="{ show: 500, hide: 100 }" v-bind="$attrs" :close-on-content-click="false">
<slot />
<template #popper>
<AccountHoverCard v-if="account" :account="account" />
</template>
</VMenu>
<slot v-else />
</span>
</template>

View File

@ -1,11 +1,11 @@
<script setup lang="ts">
import type { CommonRouteTabOption } from '../common/CommonRouteTabs.vue'
import type { CommonRouteTabOption } from '~/types'
const { t } = useI18n()
const route = useRoute()
const server = computedEager(() => route.params.server as string)
const account = computedEager(() => route.params.account as string)
const server = computed(() => route.params.server as string)
const account = computed(() => route.params.account as string)
const tabs = computed<CommonRouteTabOption[]>(() => [
{

View File

@ -94,8 +94,8 @@ defineExpose({ createEntry, removeEntry, updateEntry })
</template>
<template v-else>
<slot
v-for="item, index of items"
v-bind="{ key: item[keyProp as keyof U] }"
v-for="(item, index) of items"
v-bind="{ key: (item as U)[keyProp as keyof U] }"
:item="item as U"
:older="items[index + 1] as U"
:newer="items[index - 1] as U"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { RouteLocationRaw } from 'vue-router'
import type { CommonRouteTabMoreOption, CommonRouteTabOption } from '~/types'
const { options, command, replace, preventScrollTop = false, moreOptions } = defineProps<{
options: CommonRouteTabOption[]
@ -10,22 +10,6 @@ const { options, command, replace, preventScrollTop = false, moreOptions } = def
}>()
const { t } = useI18n()
export interface CommonRouteTabOption {
to: RouteLocationRaw
display: string
disabled?: boolean
name?: string
icon?: string
hide?: boolean
match?: boolean
}
export interface CommonRouteTabMoreOption {
options: CommonRouteTabOption[]
icon?: string
tooltip?: string
match?: boolean
}
const router = useRouter()
useCommands(() => command
@ -60,7 +44,7 @@ useCommands(() => command
<span ws-nowrap mxa sm:px2 sm:py3 py2 text-center text-secondary-light op50>{{ option.display }}</span>
</div>
</template>
<template v-if="moreOptions?.options?.length">
<template v-if="isHydrated && moreOptions?.options?.length">
<CommonDropdown placement="bottom" flex cursor-pointer mx-1.25rem>
<CommonTooltip placement="top" :content="moreOptions.tooltip || t('action.more')">
<button

View File

@ -10,6 +10,7 @@ defineProps<Props>()
<template>
<VTooltip
v-if="isHydrated"
v-bind="$attrs"
auto-hide
>

View File

@ -24,7 +24,7 @@ interface ShortcutItemGroup {
const isMac = useIsMac()
const modifierKeyName = computed(() => isMac.value ? '⌘' : 'Ctrl')
const shortcutItemGroups: ShortcutItemGroup[] = [
const shortcutItemGroups = computed<ShortcutItemGroup[]>(() => [
{
name: t('magic_keys.groups.navigation.title'),
items: [
@ -79,7 +79,7 @@ const shortcutItemGroups: ShortcutItemGroup[] = [
name: t('magic_keys.groups.media.title'),
items: [],
},
]
])
</script>
<template>

View File

@ -91,10 +91,6 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
return account
}
export function useAccountByHandle(acct: string) {
return useAsyncState(() => fetchAccountByHandle(acct), null).state
}
export function useAccountById(id?: string | null) {
return useAsyncState(() => fetchAccountById(id), null).state
}

View File

@ -362,20 +362,20 @@ export function useUserLocalStorage<T extends object>(key: string, initial: () =
// Backward compatibility, respect webDomain in acct
// In previous versions, acct was username@server instead of username@webDomain
// for example: elk@m.webtoo.ls instead of elk@webtoo.ls
// if (!all.value[id]) { // TODO: add back this condition in the future
const [username, webDomain] = id.split('@')
const server = currentServer.value
if (webDomain && server && server !== webDomain) {
const oldId = `${username}@${server}`
const outdatedSettings = all.value[oldId]
if (outdatedSettings) {
const newAllValue = { ...all.value, [id]: outdatedSettings }
delete newAllValue[oldId]
all.value = newAllValue
if (!all.value[id]) {
const [username, webDomain] = id.split('@')
const server = currentServer.value
if (webDomain && server && server !== webDomain) {
const oldId = `${username}@${server}`
const outdatedSettings = all.value[oldId]
if (outdatedSettings) {
const newAllValue = { ...all.value, [id]: outdatedSettings }
delete newAllValue[oldId]
all.value = newAllValue
}
}
all.value[id] = Object.assign(initial(), all.value[id] || {})
}
// }
all.value[id] = Object.assign(initial(), all.value[id] || {})
return all.value[id]
})
})

View File

@ -12,7 +12,7 @@ export default defineNuxtConfig({
tsConfig: {
exclude: ['../service-worker'],
vueCompilerOptions: {
target: 3.3,
target: 3.4,
},
},
},
@ -75,6 +75,11 @@ export default defineNuxtConfig({
'./composables/settings',
'./composables/tiptap/index.ts',
],
imports: [{
name: 'useI18n',
from: '~/utils/i18n',
priority: 100,
}],
injectAtEnd: true,
},
vite: {
@ -86,6 +91,20 @@ export default defineNuxtConfig({
build: {
target: 'esnext',
},
optimizeDeps: {
include: [
'@tiptap/vue-3', 'string-length', 'vue-virtual-scroller', 'emoji-mart', 'iso-639-1',
'@tiptap/extension-placeholder', '@tiptap/extension-document', '@tiptap/extension-paragraph',
'@tiptap/extension-text', '@tiptap/extension-mention', '@tiptap/extension-hard-break',
'@tiptap/extension-bold', '@tiptap/extension-italic', '@tiptap/extension-code',
'@tiptap/extension-history', 'prosemirror-state', 'browser-fs-access', 'blurhash',
'@vueuse/integrations/useFocusTrap', '@tiptap/extension-code-block', 'prosemirror-highlight',
'@tiptap/core', 'tippy.js', 'prosemirror-highlight/shiki', '@fnando/sparkline',
'@vueuse/gesture', 'github-reserved-names', 'file-saver', 'slimeform', 'vue-advanced-cropper',
'workbox-window', 'workbox-precaching', 'workbox-routing', 'workbox-cacheable-response',
'workbox-strategies', 'workbox-expiration',
],
},
},
postcss: {
plugins: {

View File

@ -150,6 +150,9 @@
"nuxt-security@0.13.1": "patches/nuxt-security@0.13.1.patch"
}
},
"resolutions": {
"vue": "^3.4.19"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},

View File

@ -11,7 +11,7 @@ definePageMeta({
})
const route = useRoute()
const id = computedEager(() => route.params.status as string)
const id = computed(() => route.params.status as string)
const main = ref<ComponentPublicInstance | null>(null)
const { data: status, pending, refresh: refreshStatus } = useAsyncData(
@ -71,7 +71,7 @@ onReactivated(() => {
<div xl:mt-4 mb="50vh" border="b base">
<template v-if="!pendingContext">
<StatusCard
v-for="comment, i of context?.ancestors" :key="comment.id"
v-for="(comment, i) of context?.ancestors" :key="comment.id"
:status="comment" :actions="comment.visibility !== 'direct'" context="account"
:has-older="true" :newer="context?.ancestors[i - 1]"
/>

View File

@ -4,7 +4,7 @@ definePageMeta({
})
const params = useRoute().params
const accountName = computedEager(() => toShortHandle(params.account as string))
const accountName = computed(() => toShortHandle(params.account as string))
const { t } = useI18n()

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
const { t } = useI18n()
const params = useRoute().params
const handle = computedEager(() => params.account as string)
const handle = computed(() => params.account as string)
definePageMeta({ name: 'account-followers' })

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
const { t } = useI18n()
const params = useRoute().params
const handle = computedEager(() => params.account as string)
const handle = computed(() => params.account as string)
definePageMeta({ name: 'account-following' })

View File

@ -2,7 +2,7 @@
import type { mastodon } from 'masto'
const params = useRoute().params
const handle = computedEager(() => params.account as string)
const handle = computed(() => params.account as string)
definePageMeta({ name: 'account-index' })

View File

@ -3,7 +3,7 @@ definePageMeta({ name: 'account-media' })
const { t } = useI18n()
const params = useRoute().params
const handle = computedEager(() => params.account as string)
const handle = computed(() => params.account as string)
const account = await fetchAccountByHandle(handle.value)

View File

@ -3,7 +3,7 @@ definePageMeta({ name: 'account-replies' })
const { t } = useI18n()
const params = useRoute().params
const handle = computedEager(() => params.account as string)
const handle = computed(() => params.account as string)
const account = await fetchAccountByHandle(handle.value)

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { CommonRouteTabOption } from '~/components/common/CommonRouteTabs.vue'
import type { CommonRouteTabOption } from '~/types'
const { t } = useI18n()

View File

@ -17,5 +17,5 @@ useHydratedHead({
<p>{{ $t('tooltip.explore_posts_intro') }}</p>
</CommonAlert>
<!-- TODO: Tabs for trending statuses, tags, and links -->
<TimelinePaginator :paginator="paginator" context="public" />
<TimelinePaginator v-if="isHydrated" :paginator="paginator" context="public" />
</template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { CommonRouteTabOption } from '~/components/common/CommonRouteTabs.vue'
import type { CommonRouteTabOption } from '~/types'
definePageMeta({
middleware: 'auth',

View File

@ -4,7 +4,7 @@ definePageMeta({
})
const params = useRoute().params
const listId = computedEager(() => params.list as string)
const listId = computed(() => params.list as string)
const paginator = useMastoClient().v1.lists.$select(listId.value).accounts.list()
</script>

View File

@ -4,7 +4,7 @@ definePageMeta({
})
const params = useRoute().params
const listId = computedEager(() => params.list as string)
const listId = computed(() => params.list as string)
const client = useMastoClient()

View File

@ -1,6 +1,4 @@
<script setup lang="ts">
const { t } = useI18n()
useHydratedHead({
@ -13,7 +11,7 @@ useHydratedHead({
<template #title>
<NuxtLink to="/public" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
<div i-ri:earth-line />
<span>{{ t('title.federated_timeline') }}</span>
<span>{{ $t('title.federated_timeline') }}</span>
</NuxtLink>
</template>

View File

@ -1,6 +1,4 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
useHydratedHead({
title: () => t('nav.search'),

View File

@ -4,7 +4,7 @@ definePageMeta({
})
const params = useRoute().params
const tagName = computedEager(() => params.tag as string)
const tagName = computed(() => params.tag as string)
const { client } = useMasto()
const { data: tag, refresh } = await useAsyncData(() => client.value.v1.tags.$select(tagName.value).fetch(), { default: () => shallowRef() })

View File

@ -1,6 +1,4 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
definePageMeta({
middleware: 'auth',
alias: ['/signin/callback'],

View File

@ -1,10 +1,7 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
import { NOTIFICATION_FILTER_TYPES } from '~/constants'
import type {
CommonRouteTabMoreOption,
CommonRouteTabOption,
} from '~/components/common/CommonRouteTabs.vue'
import type { CommonRouteTabMoreOption, CommonRouteTabOption } from '~/types'
definePageMeta({
middleware: 'auth',
@ -18,12 +15,12 @@ const tabs = computed<CommonRouteTabOption[]>(() => [
{
name: 'all',
to: '/notifications',
display: isHydrated.value ? t('tab.notifications_all') : '',
display: t('tab.notifications_all'),
},
{
name: 'mention',
to: '/notifications/mention',
display: isHydrated.value ? t('tab.notifications_mention') : '',
display: t('tab.notifications_mention'),
},
])
@ -50,13 +47,12 @@ const filterIconMap: Record<mastodon.v1.NotificationType, string> = {
'admin.report': 'i-ri:flag-line',
}
const filterText = computed(() => (`${t('tab.notifications_more_tooltip')}${filter ? `: ${t(`tab.notifications_${filter}`)}` : ''}`))
const filterText = computed(() => `${t('tab.notifications_more_tooltip')}${filter.value ? `: ${t(`tab.notifications_${filter.value}`)}` : ''}`)
const notificationFilterRoutes = computed<CommonRouteTabOption[]>(() => NOTIFICATION_FILTER_TYPES.map(
name => ({
name,
to: `/notifications/${name}`,
display: isHydrated.value ? t(`tab.notifications_${name}`) : '',
display: t(`tab.notifications_${name}`),
icon: filterIconMap[name],
match: name === filter.value,
}),
@ -74,7 +70,7 @@ const moreOptions = computed<CommonRouteTabMoreOption>(() => ({
<template #title>
<NuxtLink to="/notifications" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
<div i-ri:notification-4-line />
<span>{{ isHydrated ? t('nav.notifications') : '' }}</span>
<span>{{ t('nav.notifications') }}</span>
</NuxtLink>
</template>
@ -82,7 +78,7 @@ const moreOptions = computed<CommonRouteTabMoreOption>(() => ({
<NuxtLink
flex rounded-4 p1
hover:bg-active cursor-pointer transition-100
:title="isHydrated ? t('settings.notifications.show_btn') : ''"
:title="t('settings.notifications.show_btn')"
to="/settings/notifications"
>
<span aria-hidden="true" i-ri:notification-badge-line />

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
const { t } = useI18n()
useHydratedHead({
title: () => `${t('tab.notifications_all')} | ${t('nav.notifications')}`,
})

View File

@ -11,7 +11,7 @@ useHydratedHead({
const route = useRoute()
const isRootPath = computedEager(() => route.name === 'settings')
const isRootPath = computed(() => route.name === 'settings')
</script>
<template>
@ -22,12 +22,12 @@ const isRootPath = computedEager(() => route.name === 'settings')
<template #title>
<div timeline-title-style flex items-center gap-2 @click="$scrollToTop">
<div i-ri:settings-3-line />
<span>{{ isHydrated ? $t('nav.settings') : '' }}</span>
<span>{{ $t('nav.settings') }}</span>
</div>
</template>
<div xl:w-97 lg:w-78 w-full>
<SettingsItem
v-if="isHydrated && currentUser"
v-if="currentUser"
command
icon="i-ri:user-line"
:text="$t('settings.profile.label')"
@ -37,12 +37,12 @@ const isRootPath = computedEager(() => route.name === 'settings')
<SettingsItem
command
icon="i-ri-compasses-2-line"
:text="isHydrated ? $t('settings.interface.label') : ''"
:text="$t('settings.interface.label')"
to="/settings/interface"
:match="$route.path.startsWith('/settings/interface/')"
/>
<SettingsItem
v-if="isHydrated && currentUser"
v-if="currentUser"
command
icon="i-ri:notification-badge-line"
:text="$t('settings.notifications_settings')"
@ -52,28 +52,28 @@ const isRootPath = computedEager(() => route.name === 'settings')
<SettingsItem
command
icon="i-ri-globe-line"
:text="isHydrated ? $t('settings.language.label') : ''"
:text="$t('settings.language.label')"
to="/settings/language"
:match="$route.path.startsWith('/settings/language/')"
/>
<SettingsItem
command
icon="i-ri-equalizer-line"
:text="isHydrated ? $t('settings.preferences.label') : ''"
:text="$t('settings.preferences.label')"
to="/settings/preferences"
:match="$route.path.startsWith('/settings/preferences/')"
/>
<SettingsItem
command
icon="i-ri-group-line"
:text="isHydrated ? $t('settings.users.label') : ''"
:text="$t('settings.users.label')"
to="/settings/users"
:match="$route.path.startsWith('/settings/users/')"
/>
<SettingsItem
command
icon="i-ri:information-line"
:text="isHydrated ? $t('settings.about.label') : ''"
:text="$t('settings.about.label')"
to="/settings/about"
:match="$route.path.startsWith('/settings/about/')"
/>

View File

@ -15,20 +15,20 @@ useHydratedHead({
<MainContent back-on-small-screen>
<template #title>
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
<span>{{ isHydrated ? $t('settings.notifications.label') : '' }}</span>
<span>{{ $t('settings.notifications.label') }}</span>
</div>
</template>
<SettingsItem
command
:text="isHydrated ? $t('settings.notifications.notifications.label') : ''"
:text="$t('settings.notifications.notifications.label')"
to="/settings/notifications/notifications"
/>
<SettingsItem
command
:disabled="!pwaEnabled"
:text="isHydrated ? $t('settings.notifications.push_notifications.label') : ''"
:description="isHydrated ? $t('settings.notifications.push_notifications.description') : ''"
:text="$t('settings.notifications.push_notifications.label')"
:description="$t('settings.notifications.push_notifications.description')"
to="/settings/notifications/push-notifications"
/>
</MainContent>

View File

@ -17,7 +17,7 @@ useHydratedHead({
<MainContent back>
<template #title>
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
<span>{{ isHydrated ? $t('settings.notifications.push_notifications.label') : '' }}</span>
<span>{{ $t('settings.notifications.push_notifications.label') }}</span>
</div>
</template>
<NotificationPreferences show />

View File

@ -111,7 +111,7 @@ onReactivated(refreshInfo)
</template>
<form space-y-5 @submit.prevent="submit">
<div v-if="isHydrated && account">
<div v-if="account">
<!-- banner -->
<div of-hidden bg="gray-500/20" aspect="3">
<CommonInputImage
@ -182,7 +182,7 @@ onReactivated(refreshInfo)
<!-- metadata -->
<SettingsProfileMetadata v-if="isHydrated" v-model="form" />
<SettingsProfileMetadata v-model="form" />
<!-- actions -->
<div flex="~ gap2" justify-end>

View File

@ -14,22 +14,22 @@ useHydratedHead({
<MainContent back-on-small-screen>
<template #title>
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
<span>{{ isHydrated ? $t('settings.profile.label') : '' }}</span>
<span>{{ $t('settings.profile.label') }}</span>
</div>
</template>
<SettingsItem
command large
icon="i-ri:user-settings-line"
:text="isHydrated ? $t('settings.profile.appearance.label') : ''"
:description="isHydrated ? $t('settings.profile.appearance.description') : ''"
:text="$t('settings.profile.appearance.label')"
:description="$t('settings.profile.appearance.description')"
to="/settings/profile/appearance"
/>
<SettingsItem
command large
icon="i-ri:hashtag"
:text="isHydrated ? $t('settings.profile.featured_tags.label') : ''"
:description="isHydrated ? $t('settings.profile.featured_tags.description') : ''"
:text="$t('settings.profile.featured_tags.label')"
:description="$t('settings.profile.featured_tags.description')"
to="/settings/profile/featured-tags"
/>
<SettingsItem

View File

@ -81,12 +81,12 @@ async function importTokens() {
</div>
<div my4 border="t base" />
<button btn-text flex="~ gap-2" items-center @click="exportTokens">
<div i-ri-download-2-line />
<span block i-ri-download-2-line />
{{ $t('settings.users.export') }}
</button>
</template>
<button btn-text flex="~ gap-2" items-center @click="importTokens">
<div i-ri-upload-2-line />
<span block i-ri-upload-2-line />
{{ $t('settings.users.import') }}
</button>
</div>

View File

@ -1,5 +1,5 @@
import FloatingVue from 'floating-vue'
import { defineNuxtPlugin } from '#app'
import { defineNuxtPlugin } from '#imports'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(FloatingVue)

View File

@ -1,21 +0,0 @@
import type { VueI18n } from 'vue-i18n'
import type { LocaleObject } from 'vue-i18n-routing'
export default defineNuxtPlugin(async (nuxt) => {
const i18n = nuxt.vueApp.config.globalProperties.$i18n as VueI18n
const { setLocale, locales } = i18n
const userSettings = useUserSettings()
const lang = computed(() => userSettings.value.language)
const supportLanguages = (locales as LocaleObject[]).map(locale => locale.code)
if (!supportLanguages.includes(lang.value))
userSettings.value.language = getDefaultLanguage(supportLanguages)
if (lang.value !== i18n.locale)
await setLocale(userSettings.value.language)
watch([lang, isHydrated], () => {
if (isHydrated.value && lang.value !== i18n.locale)
setLocale(lang.value)
}, { immediate: true })
})

28
plugins/setup-i18n.ts Normal file
View File

@ -0,0 +1,28 @@
export default defineNuxtPlugin(async (nuxt) => {
const t = nuxt.vueApp.config.globalProperties.$t
const d = nuxt.vueApp.config.globalProperties.$d
const n = nuxt.vueApp.config.globalProperties.$n
nuxt.vueApp.config.globalProperties.$t = wrapI18n(t)
nuxt.vueApp.config.globalProperties.$d = wrapI18n(d)
nuxt.vueApp.config.globalProperties.$n = wrapI18n(n)
if (process.client) {
const i18n = nuxt.vueApp.config.globalProperties.$i18n as import('vue-i18n').VueI18n
const { setLocale, locales } = i18n
const userSettings = useUserSettings()
const lang = computed(() => userSettings.value.language)
const supportLanguages = (locales as import('vue-i18n-routing').LocaleObject[]).map(locale => locale.code)
if (!supportLanguages.includes(lang.value))
userSettings.value.language = getDefaultLanguage(supportLanguages)
if (lang.value !== i18n.locale)
await setLocale(userSettings.value.language)
watch([lang, isHydrated], () => {
if (isHydrated.value && lang.value !== i18n.locale)
setLocale(lang.value)
}, { immediate: true })
}
})

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import type { mastodon } from 'masto'
import type { MarkNonNullable, Mutable } from './utils'
import type { RouteLocationRaw } from '#vue-router'
export interface AppInfo {
id: string
@ -63,6 +64,22 @@ export interface ConfirmDialogLabel {
}
export type ConfirmDialogChoice = 'confirm' | 'cancel'
export interface CommonRouteTabOption {
to: RouteLocationRaw
display: string
disabled?: boolean
name?: string
icon?: string
hide?: boolean
match?: boolean
}
export interface CommonRouteTabMoreOption {
options: CommonRouteTabOption[]
icon?: string
tooltip?: string
match?: boolean
}
export interface ErrorDialogData {
title: string
messages: string[]

23
utils/i18n.ts Normal file
View File

@ -0,0 +1,23 @@
import { useI18n as useOriginalI18n } from 'vue-i18n'
export function useI18n() {
const {
t,
d,
n,
...rest
} = useOriginalI18n()
return {
...rest,
t: wrapI18n(t),
d: wrapI18n(d),
n: wrapI18n(n),
} satisfies ReturnType<typeof useOriginalI18n>
}
export function wrapI18n<T extends (...args: any[]) => any>(t: T): T {
return <T>((...args: any[]) => {
return isHydrated.value ? t(...args) : ''
})
}