Impove Intl formatting (#5372)

* Improve Intl formatting

* Additional fixes

* Fixed formatters were not updated on locale change

* Fixed formatNumber was not updated on locale change

* Additional formatting and fixes after merge

* Run prepr:frontend

* Remove `'` in icon map

* Run `pnpm install`

* fix: lint + import

* Additional fixes

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
This commit is contained in:
Jerozgen
2026-03-10 00:29:32 +03:00
committed by GitHub
parent 9b2f0c88cd
commit f62c60a681
88 changed files with 839 additions and 621 deletions

View File

@@ -178,12 +178,7 @@
class="categories"
/>
{{ $formatVersion(notif.extra_data.version.game_versions) }}
<span
v-tooltip="
$dayjs(notif.extra_data.version.date_published).format('MMMM D, YYYY [at] h:mm A')
"
class="date"
>
<span v-tooltip="formatDateTime(notif.extra_data.version.date_published)" class="date">
{{ formatRelativeTime(notif.extra_data.version.date_published) }}
</span>
</span>
@@ -197,10 +192,7 @@
<span v-if="notification.read" class="read-badge inline-flex">
<CheckCircleIcon /> Read
</span>
<span
v-tooltip="$dayjs(notification.created).format('MMMM D, YYYY [at] h:mm A')"
class="inline-flex"
>
<span v-tooltip="formatDateTime(notification.created)" class="inline-flex">
<CalendarIcon class="mr-1" /> Received
{{ formatRelativeTime(notification.created) }}
</span>
@@ -338,6 +330,7 @@ import {
DoubleIcon,
injectNotificationManager,
ProjectStatusBadge,
useFormatDateTime,
useRelativeTime,
} from '@modrinth/ui'
import { getUserLink, renderString } from '@modrinth/utils'
@@ -351,6 +344,10 @@ import ThreadSummary from './thread/ThreadSummary.vue'
const { addNotification } = injectNotificationManager()
const emit = defineEmits(['update:notifications'])
const formatRelativeTime = useRelativeTime()
const formatDateTime = useFormatDateTime({
timeStyle: 'short',
dateStyle: 'long',
})
const props = defineProps({
notification: {

View File

@@ -1,7 +1,12 @@
<script setup lang="ts">
import { FileTextIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
import { formatMoney } from '@modrinth/utils'
import {
ButtonStyled,
defineMessages,
PagewideBanner,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { computed } from 'vue'
import { getTaxThreshold } from '@/providers/creator-withdraw.ts'
@@ -9,6 +14,7 @@ import CreatorTaxFormModal from '~/components/ui/dashboard/CreatorTaxFormModal.v
import { useGeneratedState } from '~/composables/generated'
const { formatMessage } = useVIntl()
const formatMoney = useFormatMoney()
const generatedState = useGeneratedState()
const taxThreshold = computed(() => getTaxThreshold(generatedState.value?.taxComplianceThresholds))

View File

@@ -1,6 +1,5 @@
<script setup>
import { formatMoney, formatNumber } from '@modrinth/utils'
import dayjs from 'dayjs'
import { useFormatDateTime, useFormatMoney, useFormatNumber } from '@modrinth/ui'
import VueApexCharts from 'vue3-apexcharts'
const props = defineProps({
@@ -18,7 +17,6 @@ const props = defineProps({
},
formatLabels: {
type: Function,
default: (label) => dayjs(label).format('MMM D'),
},
colors: {
type: Array,
@@ -78,8 +76,15 @@ const props = defineProps({
},
})
const formatNumber = useFormatNumber()
const formatMoney = useFormatMoney()
const formatDate = useFormatDateTime({
month: 'short',
day: 'numeric',
})
function formatTooltipValue(value, props) {
return props.isMoney ? formatMoney(value, false) : formatNumber(value, false)
return props.isMoney ? formatMoney(value) : formatNumber(value)
}
function generateListEntry(value, index, _, w, props) {
@@ -99,7 +104,7 @@ function generateListEntry(value, index, _, w, props) {
function generateTooltip({ series, seriesIndex, dataPointIndex, w }, props) {
const label = w.globals.lastXAxis.categories?.[dataPointIndex]
const formattedLabel = props.formatLabels(label)
const formattedLabel = props.formatLabels ? props.formatLabels(label) : formatDate(label)
let tooltip = `<div class="bar-tooltip">
<div class="seperated-entry title">

View File

@@ -21,7 +21,7 @@
ref="tinyDownloadChart"
:title="`Downloads`"
color="var(--color-brand)"
:value="formatNumber(analytics.formattedData.value.downloads.sum, false)"
:value="formatNumber(analytics.formattedData.value.downloads.sum)"
:data="analytics.formattedData.value.downloads.chart.sumData"
:labels="analytics.formattedData.value.downloads.chart.labels"
suffix="<svg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'><path stroke-linecap='round' stroke-linejoin='round' d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4' /></svg>"
@@ -40,7 +40,7 @@
ref="tinyViewChart"
:title="`Views`"
color="var(--color-blue)"
:value="formatNumber(analytics.formattedData.value.views.sum, false)"
:value="formatNumber(analytics.formattedData.value.views.sum)"
:data="analytics.formattedData.value.views.chart.sumData"
:labels="analytics.formattedData.value.views.chart.labels"
suffix="<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z'/><circle cx='12' cy='12' r='3'/></svg>"
@@ -57,7 +57,7 @@
ref="tinyRevenueChart"
:title="`Revenue`"
color="var(--color-purple)"
:value="formatMoney(analytics.formattedData.value.revenue.sum, false)"
:value="formatMoney(analytics.formattedData.value.revenue.sum)"
:data="analytics.formattedData.value.revenue.chart.sumData"
:labels="analytics.formattedData.value.revenue.chart.labels"
is-money
@@ -221,7 +221,7 @@
><template v-if="name.toLowerCase() === 'xx' || !name">Other</template>
<template v-else>{{ countryCodeToName(name) }}</template>
</strong>
<span class="data-point">{{ formatNumber(count) }}</span>
<span class="data-point">{{ formatCompactNumber(count) }}</span>
</div>
<div
v-tooltip="
@@ -280,7 +280,7 @@
<template v-if="name.toLowerCase() === 'xx' || !name">Other</template>
<template v-else>{{ countryCodeToName(name) }}</template>
</strong>
<span class="data-point">{{ formatNumber(count) }}</span>
<span class="data-point">{{ formatCompactNumber(count) }}</span>
</div>
<div
v-tooltip="
@@ -310,8 +310,15 @@
<script setup lang="ts">
import { DownloadIcon, PaletteIcon, UpdatedIcon } from '@modrinth/assets'
import { Button, Card, DropdownSelect } from '@modrinth/ui'
import { capitalizeString, formatMoney, formatNumber } from '@modrinth/utils'
import {
Button,
Card,
DropdownSelect,
useCompactNumber,
useFormatMoney,
useFormatNumber,
} from '@modrinth/ui'
import { capitalizeString } from '@modrinth/utils'
import dayjs from 'dayjs'
import { computed } from 'vue'
@@ -325,6 +332,10 @@ import {
intToRgba,
} from '~/utils/analytics.js'
const formatNumber = useFormatNumber()
const { formatCompactNumber } = useCompactNumber()
const formatMoney = useFormatMoney()
const router = useNativeRouter()
const theme = useTheme()

View File

@@ -55,9 +55,9 @@ import {
commonMessages,
formFieldPlaceholders,
StyledInput,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { formatMoney } from '@modrinth/utils'
import { computed, ref, watch } from 'vue'
const props = withDefaults(
@@ -82,6 +82,7 @@ const emit = defineEmits<{
}>()
const { formatMessage } = useVIntl()
const formatMoney = useFormatMoney()
const amountInput = ref<InstanceType<typeof StyledInput> | null>(null)
const safeMaxAmount = computed(() => {

View File

@@ -35,7 +35,7 @@
>{{ formatTransactionStatus(transaction.status) }} <BulletDivider
/></span>
</template>
{{ dayjs(transaction.created).format('MMM DD YYYY') }}
{{ formatDate(transaction.created) }}
<template v-if="transaction.type === 'withdrawal' && transaction.fee">
<BulletDivider /> Fee {{ formatMoney(transaction.fee) }}
</template>
@@ -79,10 +79,11 @@ import {
ButtonStyled,
getCurrencyIcon,
injectNotificationManager,
useFormatDateTime,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { capitalizeString, formatMoney } from '@modrinth/utils'
import dayjs from 'dayjs'
import { capitalizeString } from '@modrinth/utils'
import { Tooltip } from 'floating-vue'
import { useGeneratedState } from '~/composables/generated'
@@ -188,6 +189,8 @@ function formatTransactionStatus(status: string): string {
}
const { formatMessage } = useVIntl()
const formatMoney = useFormatMoney()
const formatDate = useFormatDateTime({ dateStyle: 'medium' })
function formatMethodName(method: string | undefined, method_id: string | undefined): string {
if (!method) return 'Unknown'

View File

@@ -57,8 +57,7 @@
<script setup lang="ts">
import { LoaderCircleIcon } from '@modrinth/assets'
import { defineMessages, useVIntl } from '@modrinth/ui'
import { formatMoney } from '@modrinth/utils'
import { defineMessages, useFormatMoney, useVIntl } from '@modrinth/ui'
import { computed } from 'vue'
const props = withDefaults(
@@ -78,6 +77,7 @@ const props = withDefaults(
)
const { formatMessage } = useVIntl()
const formatMoney = useFormatMoney()
const amountInUsd = computed(() => {
if (props.isGiftCard && shouldShowExchangeRate.value) {
@@ -119,31 +119,13 @@ const formattedLocalCurrency = computed(() => {
if (!shouldShowExchangeRate.value || !netAmountInLocalCurrency.value || !props.localCurrency)
return ''
try {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: props.localCurrency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(netAmountInLocalCurrency.value)
} catch {
return `${props.localCurrency} ${netAmountInLocalCurrency.value.toFixed(2)}`
}
return formatMoney(netAmountInLocalCurrency.value, props.localCurrency)
})
const formattedLocalCurrencyAmount = computed(() => {
if (!shouldShowExchangeRate.value || !localCurrencyAmount.value || !props.localCurrency) return ''
try {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: props.localCurrency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(localCurrencyAmount.value)
} catch {
return `${props.localCurrency} ${localCurrencyAmount.value.toFixed(2)}`
}
return formatMoney(localCurrencyAmount.value, props.localCurrency)
})
const messages = defineMessages({

View File

@@ -124,9 +124,14 @@
</template>
<script setup lang="ts">
import { defineMessages, IntlFormatted, normalizeChildren, useVIntl } from '@modrinth/ui'
import { formatMoney } from '@modrinth/utils'
import dayjs from 'dayjs'
import {
defineMessages,
IntlFormatted,
normalizeChildren,
useFormatDateTime,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { computed, onMounted, ref } from 'vue'
import ConfettiExplosion from 'vue-confetti-explosion'
@@ -135,6 +140,8 @@ import { getRailConfig } from '@/utils/muralpay-rails'
const { withdrawData } = useWithdrawContext()
const { formatMessage } = useVIntl()
const formatMoney = useFormatMoney()
const formatDate = useFormatDateTime({ dateStyle: 'long' })
const result = computed(() => withdrawData.value.result)
@@ -149,7 +156,7 @@ onMounted(() => {
const formattedDate = computed(() => {
if (!result.value?.created) return 'N/A'
return dayjs(result.value.created).format('MMMM D, YYYY')
return formatDate(result.value.created)
})
const selectedRail = computed(() => {
@@ -185,16 +192,7 @@ const formattedLocalCurrency = computed(() => {
if (!shouldShowExchangeRate.value || !netAmountInLocalCurrency.value || !localCurrency.value)
return ''
try {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: localCurrency.value,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(netAmountInLocalCurrency.value)
} catch {
return `${localCurrency.value} ${netAmountInLocalCurrency.value.toFixed(2)}`
}
return formatMoney(netAmountInLocalCurrency.value, localCurrency.value)
})
const isMuralPayWithdrawal = computed(() => {

View File

@@ -88,9 +88,9 @@ import {
IntlFormatted,
normalizeChildren,
useDebugLogger,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { formatMoney } from '@modrinth/utils'
import { useGeolocation } from '@vueuse/core'
import { useCountries, useFormattedCountries, useUserCountry } from '@/composables/country.ts'
@@ -115,6 +115,7 @@ const userCountry = useUserCountry()
const allCountries = useCountries()
const { coords } = useGeolocation()
const { formatMessage } = useVIntl()
const formatMoney = useFormatMoney()
const { addNotification } = injectNotificationManager()
const auth = await useAuth()

View File

@@ -80,9 +80,9 @@ import {
defineMessages,
IntlFormatted,
normalizeChildren,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { formatMoney } from '@modrinth/utils'
import { computed } from 'vue'
import { getTaxThreshold, getTaxThresholdActual } from '@/providers/creator-withdraw.ts'
@@ -94,6 +94,7 @@ const props = defineProps<{
}>()
const { formatMessage } = useVIntl()
const formatMoney = useFormatMoney()
const generatedState = useGeneratedState()
const taxThreshold = computed(() => getTaxThreshold(generatedState.value.taxComplianceThresholds))

View File

@@ -350,9 +350,9 @@ import {
paymentMethodMessages,
StyledInput,
useDebugLogger,
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
import { formatMoney } from '@modrinth/utils'
import { useDebounceFn } from '@vueuse/core'
import { computed, onMounted, ref, watch } from 'vue'
@@ -365,6 +365,7 @@ const debug = useDebugLogger('TremendousDetailsStage')
const { withdrawData, maxWithdrawAmount, availableMethods, paymentOptions, calculateFees } =
useWithdrawContext()
const { formatMessage } = useVIntl()
const formatMoney = useFormatMoney()
const auth = await useAuth()
const userEmail = computed(() => {
@@ -587,16 +588,7 @@ function formatAmountForDisplay(
if (!currencyCode || currencyCode === 'USD' || !rate) {
return formatMoney(localAmount)
}
try {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currencyCode,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(localAmount)
} catch {
return `${currencyCode} ${localAmount.toFixed(2)}`
}
return formatMoney(localAmount, currencyCode)
}
const useFixedDenominations = computed(() => {

View File

@@ -73,7 +73,7 @@
<div class="flex items-center gap-3">
<span
v-tooltip="`Since ${queuedDate.toLocaleString()}`"
v-tooltip="`Since ${formatDateTimeFull(queuedDate.toDate())}`"
class="text-base text-secondary"
:class="{
'text-red': daysInQueue > 4,
@@ -120,6 +120,7 @@ import {
injectNotificationManager,
OverflowMenu,
type OverflowMenuOption,
useFormatDateTime,
useRelativeTime,
} from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
@@ -130,6 +131,17 @@ import type { ModerationProject } from '~/helpers/moderation'
const { addNotification } = injectNotificationManager()
const formatRelativeTime = useRelativeTime()
const formatDateTimeFull = useFormatDateTime({
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short',
timeZone: 'UTC',
})
const baseId = useId()

View File

@@ -32,7 +32,7 @@
<div class="flex flex-row items-center gap-2 self-end sm:self-auto">
<span
v-tooltip="formatExactDate(report.created)"
v-tooltip="formatDateTime(report.created)"
class="cursor-help whitespace-nowrap text-sm text-secondary"
>
{{ formatRelativeTime(report.created) }}
@@ -80,7 +80,7 @@
<span
v-if="report.user?.created"
v-tooltip="formatExactDate(report.user.created)"
v-tooltip="formatDateTime(report.user.created)"
class="cursor-help text-sm text-secondary"
>
Joined {{ formatRelativeTime(report.user.created) }}
@@ -190,7 +190,7 @@ import {
LinkIcon,
} from '@modrinth/assets'
import { type ExtendedReport, reportQuickReplies } from '@modrinth/moderation'
import type { OverflowMenuOption } from '@modrinth/ui'
import { type OverflowMenuOption, useFormatDateTime } from '@modrinth/ui'
import {
Avatar,
ButtonStyled,
@@ -201,7 +201,6 @@ import {
useRelativeTime,
} from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
import dayjs from 'dayjs'
import { computed } from 'vue'
import { isStaff } from '~/helpers/users.js'
@@ -305,10 +304,10 @@ async function reopenReport() {
}
const formatRelativeTime = useRelativeTime()
function formatExactDate(date: string): string {
return dayjs(date).format('MMMM D, YYYY [at] h:mm A')
}
const formatDateTime = useFormatDateTime({
timeStyle: 'short',
dateStyle: 'long',
})
function updateThread(newThread: any) {
if (props.report.thread) {

View File

@@ -28,6 +28,7 @@ import {
injectNotificationManager,
OverflowMenu,
type OverflowMenuOption,
useFormatDateTime,
} from '@modrinth/ui'
import {
capitalizeString,
@@ -46,6 +47,16 @@ import ThreadView from '~/components/ui/thread/ThreadView.vue'
const auth = await useAuth()
const featureFlags = useFeatureFlags()
const formatDateTimeUtc = useFormatDateTime({
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short',
timeZone: 'UTC',
})
type FlattenedFileReport = Labrinth.TechReview.Internal.FileReport & {
id: string
version_id: string
@@ -763,7 +774,7 @@ const reviewSummaryPreview = computed(() => {
const totalDecisions = totalSafe + totalUnsafe
if (totalDecisions === 0) return ''
const timestamp = dayjs().utc().format('MMMM D, YYYY [at] h:mm A [UTC]')
const timestamp = formatDateTimeUtc(dayjs().toDate())
let markdown = `## Tech Review Summary\n*${timestamp}*\n\n`
markdown += `<details>\n<summary>File Details (${totalSafe} safe, ${totalUnsafe} unsafe)</summary>\n\n`

View File

@@ -94,7 +94,7 @@
<span>{{ report.reporterUser.username }}</span>
</nuxt-link>
<span>&nbsp;</span>
<span v-tooltip="$dayjs(report.created).format('MMMM D, YYYY [at] h:mm A')">{{
<span v-tooltip="formatDateTime(report.created)">{{
formatRelativeTime(report.created)
}}</span>
<CopyCode v-if="flags.developerMode" :text="report.id" class="report-id" />
@@ -104,13 +104,17 @@
<script setup>
import { ReportIcon, UnknownIcon, VersionIcon } from '@modrinth/assets'
import { Avatar, Badge, CopyCode, useRelativeTime } from '@modrinth/ui'
import { Avatar, Badge, CopyCode, useFormatDateTime, useRelativeTime } from '@modrinth/ui'
import { formatProjectType, renderHighlightedString } from '@modrinth/utils'
import ThreadSummary from '~/components/ui/thread/ThreadSummary.vue'
import { getProjectTypeForUrl } from '~/helpers/projects.js'
const formatRelativeTime = useRelativeTime()
const formatDateTime = useFormatDateTime({
timeStyle: 'short',
dateStyle: 'long',
})
defineProps({
report: {

View File

@@ -79,6 +79,7 @@ import {
getFileExtensionIcon,
isEditableFile as isEditableFileExt,
isImageFile,
useFormatDateTime,
} from '@modrinth/ui'
import { computed, h, ref, shallowRef } from 'vue'
import { renderToString } from 'vue/server-renderer'
@@ -121,6 +122,14 @@ const units = Object.freeze(['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'])
const route = shallowRef(useRoute())
const router = useRouter()
const formatDateTime = useFormatDateTime({
year: '2-digit',
month: '2-digit',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
})
const containerClasses = computed(() => [
'group m-0 flex w-full select-none items-center justify-between overflow-hidden border-0 border-t border-solid border-surface-3 px-4 py-3 focus:!outline-none',
props.index % 2 === 0 ? 'bg-surface-2' : 'bg-surface-3',
@@ -177,28 +186,12 @@ const iconComponent = computed(() => {
const formattedModifiedDate = computed(() => {
const date = new Date(props.modified * 1000)
return `${date.toLocaleDateString('en-US', {
month: '2-digit',
day: '2-digit',
year: '2-digit',
})}, ${date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
hour12: true,
})}`
return formatDateTime(date)
})
const formattedCreationDate = computed(() => {
const date = new Date(props.created * 1000)
return `${date.toLocaleDateString('en-US', {
month: '2-digit',
day: '2-digit',
year: '2-digit',
})}, ${date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
hour12: true,
})}`
return formatDateTime(date)
})
const isEditableFile = computed(() => {

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import type { MessageDescriptor } from '@modrinth/ui'
import { type MessageDescriptor, useFormatPrice } from '@modrinth/ui'
import { ButtonStyled, defineMessage, defineMessages, ServersSpecs, useVIntl } from '@modrinth/ui'
import { formatPrice } from '@modrinth/utils'
const { formatMessage, locale } = useVIntl()
const { formatMessage } = useVIntl()
const formatPrice = useFormatPrice()
const emit = defineEmits<{
(e: 'select' | 'scroll-to-faq'): void
@@ -132,7 +132,7 @@ const billingMonths = computed(() => {
</div>
</div>
<span class="m-0 text-2xl font-bold text-contrast">
{{ formatPrice(locale, price / billingMonths, currency, true) }}
{{ formatPrice(price / billingMonths, currency, true) }}
<span class="text-lg font-semibold text-secondary">
/ month<template v-if="interval !== 'monthly'">, billed {{ interval }}</template>
</span>

View File

@@ -6,14 +6,22 @@ import {
NOTICE_LEVELS,
ServerNotice,
TagItem,
useFormatDateTime,
useRelativeTime,
useVIntl,
} from '@modrinth/ui'
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
import dayjs from 'dayjs'
const { formatMessage } = useVIntl()
const formatRelativeTime = useRelativeTime()
const formatDateTime = useFormatDateTime({
timeStyle: 'short',
dateStyle: 'long',
})
const formatDateTimeShortMonth = useFormatDateTime({
timeStyle: 'short',
dateStyle: 'medium',
})
defineProps<{
notice: ServerNoticeType
@@ -27,17 +35,14 @@ defineProps<{
</div>
<div class="text-sm">
<span v-if="notice.announce_at">
{{ dayjs(notice.announce_at).format('MMM D, YYYY [at] h:mm A') }} ({{
{{ formatDateTimeShortMonth(notice.announce_at) }} ({{
formatRelativeTime(notice.announce_at)
}})
</span>
<template v-else> Never begins </template>
</div>
<div class="text-sm">
<span
v-if="notice.expires"
v-tooltip="dayjs(notice.expires).format('MMMM D, YYYY [at] h:mm A')"
>
<span v-if="notice.expires" v-tooltip="formatDateTime(notice.expires)">
{{ formatRelativeTime(notice.expires) }}
</span>
<template v-else> Never expires </template>

View File

@@ -99,7 +99,7 @@
</span>
</div>
<span class="message__date">
<span v-tooltip="$dayjs(message.created).format('MMMM D, YYYY [at] h:mm A')">
<span v-tooltip="formatDateTime(message.created)">
{{ timeSincePosted }}
</span>
</span>
@@ -131,7 +131,14 @@ import {
ScaleIcon,
TrashIcon,
} from '@modrinth/assets'
import { AutoLink, Avatar, Badge, OverflowMenu, useRelativeTime } from '@modrinth/ui'
import {
AutoLink,
Avatar,
Badge,
OverflowMenu,
useFormatDateTime,
useRelativeTime,
} from '@modrinth/ui'
import { renderString } from '@modrinth/utils'
import { isStaff } from '~/helpers/users.js'
@@ -186,6 +193,11 @@ const formattedMessage = computed(() => {
})
const formatRelativeTime = useRelativeTime()
const formatDateTime = useFormatDateTime({
timeStyle: 'short',
dateStyle: 'long',
})
const timeSincePosted = ref(formatRelativeTime(props.message.created))
const isPrivateMessage = computed(() => {