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:
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { SpinnerIcon } from '@modrinth/assets'
|
||||
import { formatPrice } from '@modrinth/utils'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useVIntl } from '../../composables/i18n'
|
||||
import { useFormatPrice } from '../../composables'
|
||||
import Accordion from '../base/Accordion.vue'
|
||||
|
||||
const { locale } = useVIntl()
|
||||
const formatPrice = useFormatPrice()
|
||||
|
||||
export type BillingItem = {
|
||||
title: string
|
||||
@@ -38,7 +37,7 @@ const periodSuffix = computed(() => {
|
||||
<template v-if="loading">
|
||||
<SpinnerIcon class="animate-spin size-4" />
|
||||
</template>
|
||||
<template v-else> {{ formatPrice(locale, total, currency) }} </template
|
||||
<template v-else> {{ formatPrice(total, currency) }} </template
|
||||
><span class="text-xs text-secondary">{{ periodSuffix }}</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -57,7 +56,7 @@ const periodSuffix = computed(() => {
|
||||
<template v-if="loading">
|
||||
<SpinnerIcon class="animate-spin size-4" />
|
||||
</template>
|
||||
<template v-else> {{ formatPrice(locale, amount, currency) }} </template
|
||||
<template v-else> {{ formatPrice(amount, currency) }} </template
|
||||
><span class="text-xs text-secondary">{{ periodSuffix }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { InfoIcon } from '@modrinth/assets'
|
||||
import { formatPrice } from '@modrinth/utils'
|
||||
import { Menu } from 'floating-vue'
|
||||
import { computed, inject, type Ref } from 'vue'
|
||||
|
||||
import { useFormatPrice } from '../../composables'
|
||||
import { type MessageDescriptor, useVIntl } from '../../composables/i18n'
|
||||
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
||||
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
||||
@@ -30,7 +30,8 @@ const emit = defineEmits<{
|
||||
(e: 'select', plan: Labrinth.Billing.Internal.Product): void
|
||||
}>()
|
||||
|
||||
const { formatMessage, locale } = useVIntl()
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatPrice = useFormatPrice()
|
||||
|
||||
// TODO: Use DI framework when merged.
|
||||
const selectedInterval = inject<Ref<ServerBillingInterval>>('selectedInterval')
|
||||
@@ -101,7 +102,7 @@ const mostPopularStyle = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
<span class="m-0 text-lg font-bold text-contrast">
|
||||
{{ formatPrice(locale, perMonth, currency, true) }}
|
||||
{{ formatPrice(perMonth, currency, true) }}
|
||||
<span class="text-sm font-semibold text-secondary">
|
||||
/ month{{ selectedInterval !== 'monthly' ? `, billed ${selectedInterval}` : '' }}
|
||||
</span>
|
||||
|
||||
@@ -232,7 +232,7 @@
|
||||
}}%
|
||||
</span>
|
||||
<span class="ml-auto text-lg" :class="{ 'text-secondary': selectedPlan !== interval }">
|
||||
{{ formatPrice(locale, rawPrice, price.currency_code) }}
|
||||
{{ formatPrice(rawPrice, price.currency_code) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,7 +240,7 @@
|
||||
<span class="text-xl text-secondary">Total</span>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-2xl font-extrabold text-primary">
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||
{{ formatPrice(price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||
</span>
|
||||
<span class="text-lg text-secondary">/ {{ selectedPlan }}</span>
|
||||
</div>
|
||||
@@ -304,23 +304,21 @@
|
||||
}}
|
||||
</span>
|
||||
<span v-if="existingPlan" class="text-secondary text-end">
|
||||
{{ formatPrice(locale, total - tax, price.currency_code) }}
|
||||
{{ formatPrice(total - tax, price.currency_code) }}
|
||||
</span>
|
||||
<span v-else class="text-secondary text-end">
|
||||
{{ formatPrice(locale, total - tax, price.currency_code) }} /
|
||||
{{ formatPrice(total - tax, price.currency_code) }} /
|
||||
{{ selectedPlan }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-secondary">Tax</span>
|
||||
<span class="text-secondary text-end">{{
|
||||
formatPrice(locale, tax, price.currency_code)
|
||||
}}</span>
|
||||
<span class="text-secondary text-end">{{ formatPrice(tax, price.currency_code) }}</span>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-between border-0 border-t border-solid border-code-bg pt-4">
|
||||
<span class="text-lg font-bold">Today's total</span>
|
||||
<span class="text-lg font-extrabold text-primary text-end">
|
||||
{{ formatPrice(locale, total, price.currency_code) }}
|
||||
{{ formatPrice(total, price.currency_code) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -416,9 +414,9 @@
|
||||
<strong>By clicking "Subscribe", you are purchasing a recurring subscription.</strong>
|
||||
<br />
|
||||
You'll be charged
|
||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||
{{ formatPrice(price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||
/ {{ selectedPlan }} plus applicable taxes starting
|
||||
{{ existingPlan ? dayjs(renewalDate).format('MMMM D, YYYY') : 'today' }}, until you cancel.
|
||||
{{ existingPlan ? formatDate(renewalDate) : 'today' }}, until you cancel.
|
||||
<br />
|
||||
You can cancel anytime from your settings page.
|
||||
</p>
|
||||
@@ -545,12 +543,13 @@ import {
|
||||
UnknownIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { calculateSavings, createStripeElements, formatPrice, getCurrency } from '@modrinth/utils'
|
||||
import { calculateSavings, createStripeElements, getCurrency } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue'
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
|
||||
import { useVIntl } from '../../composables/i18n'
|
||||
import { useFormatDateTime, useFormatPrice } from '../../composables/index.ts'
|
||||
import { paymentMethodMessages } from '../../utils/common-messages'
|
||||
import Admonition from '../base/Admonition.vue'
|
||||
import Checkbox from '../base/Checkbox.vue'
|
||||
@@ -560,7 +559,9 @@ import AnimatedLogo from '../brand/AnimatedLogo.vue'
|
||||
import NewModal from '../modal/NewModal.vue'
|
||||
import LoaderIcon from '../servers/icons/LoaderIcon.vue'
|
||||
|
||||
const { locale, formatMessage } = useVIntl()
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatPrice = useFormatPrice()
|
||||
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||
|
||||
const props = defineProps({
|
||||
product: {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { formatPrice } from '@modrinth/utils'
|
||||
import { computed, provide } from 'vue'
|
||||
|
||||
import { useFormatPrice } from '../../composables'
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
||||
import OptionGroup from '../base/OptionGroup.vue'
|
||||
import ModalBasedServerPlan from './ModalBasedServerPlan.vue'
|
||||
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
||||
|
||||
const { formatMessage, locale } = useVIntl()
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatPrice = useFormatPrice()
|
||||
|
||||
const props = defineProps<{
|
||||
availableProducts: Labrinth.Billing.Internal.Product[]
|
||||
@@ -209,7 +210,7 @@ provide('selectedInterval', selectedInterval)
|
||||
<span class="text-2xl font-semibold text-contrast">Custom</span>
|
||||
</div>
|
||||
<span class="m-0 text-lg font-bold text-contrast">
|
||||
{{ formatPrice(locale, customStartingPrice, currency, true) }}
|
||||
{{ formatPrice(customStartingPrice, currency, true) }}
|
||||
<span class="text-sm font-semibold text-secondary">
|
||||
/ month<template v-if="selectedInterval !== 'monthly'"
|
||||
>, billed {{ selectedInterval }}</template
|
||||
@@ -221,7 +222,7 @@ provide('selectedInterval', selectedInterval)
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<span v-if="customPricePerGb" class="text-sm text-secondary">
|
||||
From {{ formatPrice(locale, customPricePerGb, currency, true) }} / GB
|
||||
From {{ formatPrice(customPricePerGb, currency, true) }} / GB
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Archon, Labrinth } from '@modrinth/api-client'
|
||||
import { InfoIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { formatPrice } from '../../../../utils'
|
||||
import { useFormatPrice } from '../../composables'
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils.ts'
|
||||
import { regionOverrides } from '../../utils/regions.ts'
|
||||
@@ -14,7 +14,8 @@ import type { RegionPing, ServerBillingInterval } from './ModrinthServersPurchas
|
||||
import ServersRegionButton from './ServersRegionButton.vue'
|
||||
import ServersSpecs from './ServersSpecs.vue'
|
||||
|
||||
const { formatMessage, locale } = useVIntl()
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatPrice = useFormatPrice()
|
||||
|
||||
const props = defineProps<{
|
||||
regions: Archon.Servers.v1.Region[]
|
||||
@@ -283,7 +284,7 @@ onMounted(() => {
|
||||
<Slider v-model="selectedRam" :min="minRam" :max="maxRam" :step="2" unit="GB" />
|
||||
<p v-if="selectedPrice" class="mt-2 mb-0">
|
||||
<span class="text-contrast text-lg font-bold"
|
||||
>{{ formatPrice(locale, selectedPrice, currency, true) }} / month</span
|
||||
>{{ formatPrice(selectedPrice, currency, true) }} / month</span
|
||||
><span v-if="interval !== 'monthly'">, billed {{ interval }}</span>
|
||||
</p>
|
||||
<div class="bg-bg rounded-xl p-4 mt-2 text-secondary h-14">
|
||||
|
||||
@@ -10,11 +10,12 @@ import {
|
||||
SpinnerIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { formatPrice, getPingLevel } from '@modrinth/utils'
|
||||
import { getPingLevel } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import type Stripe from 'stripe'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useFormatPrice } from '../../composables'
|
||||
import { useVIntl } from '../../composables/i18n'
|
||||
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
||||
import { regionOverrides } from '../../utils/regions'
|
||||
@@ -27,8 +28,8 @@ import FormattedPaymentMethod from './FormattedPaymentMethod.vue'
|
||||
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
||||
import ServersSpecs from './ServersSpecs.vue'
|
||||
|
||||
const vintl = useVIntl()
|
||||
const { locale, formatMessage } = vintl
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatPrice = useFormatPrice()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'changePaymentMethod' | 'reloadPaymentIntent'): void
|
||||
@@ -246,7 +247,7 @@ function setInterval(newInterval: ServerBillingInterval) {
|
||||
>Pay monthly</span
|
||||
>
|
||||
<span class="text-sm text-secondary flex items-center gap-1"
|
||||
>{{ formatPrice(locale, monthlyPrice, currency, true) }} / month</span
|
||||
>{{ formatPrice(monthlyPrice, currency, true) }} / month</span
|
||||
>
|
||||
</div>
|
||||
</button>
|
||||
@@ -268,17 +269,10 @@ function setInterval(newInterval: ServerBillingInterval) {
|
||||
>{{ interval === 'quarterly' ? 'Saving' : 'Save' }} 16%</span
|
||||
></span
|
||||
>
|
||||
<span class="text-sm text-secondary flex items-center gap-1"
|
||||
>{{
|
||||
formatPrice(
|
||||
locale,
|
||||
(quarterlyPrice ?? 0) / monthsInInterval['quarterly'],
|
||||
currency,
|
||||
true,
|
||||
)
|
||||
}}
|
||||
/ month</span
|
||||
>
|
||||
<span class="text-sm text-secondary flex items-center gap-1">
|
||||
{{ formatPrice((quarterlyPrice ?? 0) / monthsInInterval['quarterly'], currency, true) }} /
|
||||
month
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -346,14 +340,14 @@ function setInterval(newInterval: ServerBillingInterval) {
|
||||
Today, you will be charged a prorated amount for the remainder of your current billing cycle.
|
||||
<br />
|
||||
Your subscription will renew at
|
||||
{{ formatPrice(locale, selectedPlanPriceForInterval, currency) }} / {{ period }} plus
|
||||
applicable taxes at the end of your current billing interval, until you cancel. You can cancel
|
||||
anytime from your settings page.
|
||||
{{ formatPrice(selectedPlanPriceForInterval, currency) }} / {{ period }} plus applicable taxes
|
||||
at the end of your current billing interval, until you cancel. You can cancel anytime from
|
||||
your settings page.
|
||||
</template>
|
||||
<template v-else>
|
||||
You'll be charged
|
||||
<SpinnerIcon v-if="loading" class="animate-spin relative top-0.5 mx-2" /><template v-else>{{
|
||||
formatPrice(locale, total, currency)
|
||||
formatPrice(total, currency)
|
||||
}}</template>
|
||||
every {{ period }} plus applicable taxes starting today, until you cancel. You can cancel
|
||||
anytime from your settings page.
|
||||
|
||||
@@ -47,12 +47,19 @@ import type { VersionEntry } from '@modrinth/utils/changelog'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useRelativeTime } from '../../composables'
|
||||
import { useFormatDateTime, useRelativeTime } from '../../composables'
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import AutoLink from '../base/AutoLink.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
const formatDate = useFormatDateTime({
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -71,10 +78,10 @@ const props = withDefaults(
|
||||
const currentDate = ref(dayjs())
|
||||
const recent = computed(() => props.entry.date.isAfter(currentDate.value.subtract(1, 'week')))
|
||||
const future = computed(() => props.entry.date.isAfter(currentDate.value))
|
||||
const dateTooltip = computed(() => props.entry.date.format('MMMM D, YYYY [at] h:mm A'))
|
||||
const dateTooltip = computed(() => formatDateTime(props.entry.date.toDate()))
|
||||
|
||||
const relativeDate = computed(() => formatRelativeTime(props.entry.date.toISOString()))
|
||||
const longDate = computed(() => props.entry.date.format('MMMM D, YYYY'))
|
||||
const relativeDate = computed(() => formatRelativeTime(props.entry.date.toDate()))
|
||||
const longDate = computed(() => formatDate(props.entry.date.toDate()))
|
||||
const versionName = computed(() => props.entry.version ?? longDate.value)
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<!-- eslint-disable no-console -->
|
||||
<script setup>
|
||||
import { formatNumber } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { defineAsyncComponent, ref } from 'vue'
|
||||
|
||||
import { useFormatNumber } from '../../composables/index.ts'
|
||||
import Button from '../base/Button.vue'
|
||||
import Checkbox from '../base/Checkbox.vue'
|
||||
|
||||
const VueApexCharts = defineAsyncComponent(() => import('vue3-apexcharts'))
|
||||
const formatNumber = useFormatNumber()
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
@@ -149,7 +150,7 @@ const chartOptions = ref({
|
||||
!props.hideTotal
|
||||
? `<div class="value">
|
||||
${props.prefix}
|
||||
${formatNumber(series.reduce((a, b) => a + b[dataPointIndex], 0).toString(), false)}
|
||||
${formatNumber(series.reduce((a, b) => a + b[dataPointIndex], 0).toString())}
|
||||
${props.suffix}
|
||||
</div>`
|
||||
: ``
|
||||
@@ -163,7 +164,7 @@ const chartOptions = ref({
|
||||
</div>
|
||||
<div class="value">
|
||||
${props.prefix}
|
||||
${formatNumber(value[dataPointIndex], false)}
|
||||
${formatNumber(value[dataPointIndex])}
|
||||
${props.suffix}
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<!-- eslint-disable eslint-comments/require-description -->
|
||||
<script setup>
|
||||
import { formatNumber } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { defineAsyncComponent, ref } from 'vue'
|
||||
|
||||
import { useFormatNumber } from '../../composables/index.ts'
|
||||
import Card from '../base/Card.vue'
|
||||
|
||||
const VueApexCharts = defineAsyncComponent(() => import('vue3-apexcharts'))
|
||||
|
||||
const formatNumber = useFormatNumber()
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
@@ -124,7 +126,7 @@ const chartOptions = ref({
|
||||
</div>
|
||||
<div class="value">
|
||||
${props.prefix}
|
||||
${formatNumber(value[dataPointIndex], false)}
|
||||
${formatNumber(value[dataPointIndex])}
|
||||
${props.suffix}
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { useFormatDateTime } from '../../composables'
|
||||
import AutoLink from '../base/AutoLink.vue'
|
||||
|
||||
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||
|
||||
export interface Article {
|
||||
path: string
|
||||
thumbnail: string
|
||||
@@ -34,7 +35,7 @@ defineProps<{
|
||||
{{ article.summary }}
|
||||
</p>
|
||||
<div class="mt-auto text-sm text-secondary">
|
||||
{{ dayjs(article.date).format('MMMM D, YYYY') }}
|
||||
{{ formatDate(article.date) }}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { computed, getCurrentInstance } from 'vue'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
|
||||
import { useCompactNumber } from '../../composables'
|
||||
import { useRelativeTime } from '../../composables/how-ago'
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import { commonMessages } from '../../utils/common-messages'
|
||||
@@ -66,11 +67,7 @@ const hasContentListener = computed(() => typeof instance?.vnode.props?.onConten
|
||||
const hasUnlinkListener = computed(() => typeof instance?.vnode.props?.onUnlink === 'function')
|
||||
|
||||
const formatTimeAgo = useRelativeTime()
|
||||
|
||||
const formatCompact = (n: number | undefined) => {
|
||||
if (n === undefined) return ''
|
||||
return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 2 }).format(n)
|
||||
}
|
||||
const { formatCompactNumber } = useCompactNumber()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -165,12 +162,12 @@ const formatCompact = (n: number | undefined) => {
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div v-if="project.downloads !== undefined" class="flex items-center gap-2 text-secondary">
|
||||
<DownloadIcon class="size-5" />
|
||||
<span class="font-medium">{{ formatCompact(project.downloads) }}</span>
|
||||
<span class="font-medium">{{ formatCompactNumber(project.downloads) }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="project.followers !== undefined" class="flex items-center gap-2 text-secondary">
|
||||
<HeartIcon class="size-5" />
|
||||
<span class="font-medium">{{ formatCompact(project.followers) }}</span>
|
||||
<span class="font-medium">{{ formatCompactNumber(project.followers) }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="categories?.length" class="flex flex-wrap gap-2">
|
||||
|
||||
@@ -180,6 +180,7 @@ import {
|
||||
import { capitalizeString, renderHighlightedString } from '@modrinth/utils'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useFormatDateTime } from '../../../composables'
|
||||
import { defineMessages, useVIntl } from '../../../composables/i18n'
|
||||
import { commonMessages } from '../../../utils/common-messages'
|
||||
import Avatar from '../../base/Avatar.vue'
|
||||
@@ -188,6 +189,7 @@ import StyledInput from '../../base/StyledInput.vue'
|
||||
import NewModal from '../../modal/NewModal.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||
|
||||
const messages = defineMessages({
|
||||
updateVersionHeader: {
|
||||
@@ -341,11 +343,7 @@ function getBadgeClasses(version: Labrinth.Versions.v2.Version): string {
|
||||
}
|
||||
|
||||
function formatLongDate(dateString: string): string {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})
|
||||
return formatDate(new Date(dateString))
|
||||
}
|
||||
|
||||
function formatLoaderGameVersion(version: Labrinth.Versions.v2.Version): string {
|
||||
|
||||
@@ -50,7 +50,7 @@ const messages = defineMessages({
|
||||
},
|
||||
searchPlaceholder: {
|
||||
id: 'instances.modpack-content-modal.search-placeholder',
|
||||
defaultMessage: 'Search {count} projects',
|
||||
defaultMessage: 'Search {count, number} {count, plural, one {project} other {projects}}',
|
||||
},
|
||||
loading: {
|
||||
id: 'instances.modpack-content-modal.loading',
|
||||
@@ -82,7 +82,7 @@ const messages = defineMessages({
|
||||
},
|
||||
selectedCount: {
|
||||
id: 'instances.modpack-content-modal.selected-count',
|
||||
defaultMessage: '{count} selected',
|
||||
defaultMessage: '{count, number} selected',
|
||||
},
|
||||
enable: {
|
||||
id: 'instances.modpack-content-modal.enable',
|
||||
|
||||
@@ -27,20 +27,20 @@
|
||||
v-tooltip="
|
||||
capitalizeString(
|
||||
formatMessage(commonMessages.projectDownloads, {
|
||||
count: formatNumber(project.downloads, false),
|
||||
count: project.downloads,
|
||||
}),
|
||||
)
|
||||
"
|
||||
class="flex items-center gap-2 font-semibold cursor-help"
|
||||
>
|
||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||
{{ formatNumber(project.downloads) }}
|
||||
{{ formatCompactNumber(project.downloads) }}
|
||||
</div>
|
||||
<div
|
||||
v-tooltip="
|
||||
capitalizeString(
|
||||
formatMessage(commonMessages.projectFollowers, {
|
||||
count: formatNumber(project.followers, false),
|
||||
count: project.followers,
|
||||
}),
|
||||
)
|
||||
"
|
||||
@@ -49,7 +49,7 @@
|
||||
>
|
||||
<HeartIcon class="h-6 w-6 text-secondary" />
|
||||
<span class="font-semibold">
|
||||
{{ formatNumber(project.followers) }}
|
||||
{{ formatCompactNumber(project.followers) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -74,11 +74,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { DownloadIcon, HeartIcon } from '@modrinth/assets'
|
||||
import { capitalizeString, formatNumber, type Project } from '@modrinth/utils'
|
||||
import { capitalizeString, type Project } from '@modrinth/utils'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useVIntl } from '../../composables'
|
||||
import { useCompactNumber, useVIntl } from '../../composables'
|
||||
import { commonMessages } from '../../utils'
|
||||
import Avatar from '../base/Avatar.vue'
|
||||
import ContentPageHeader from '../base/ContentPageHeader.vue'
|
||||
@@ -89,6 +89,7 @@ import ServerDetails from './server/ServerDetails.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { formatMessage } = useVIntl()
|
||||
const { formatCompactNumber } = useCompactNumber()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
||||
@@ -171,12 +171,7 @@
|
||||
class="flex flex-col justify-center gap-1 max-sm:flex-row max-sm:justify-start max-sm:gap-3 xl:contents"
|
||||
>
|
||||
<div
|
||||
v-tooltip="
|
||||
formatMessage(commonMessages.dateAtTimeTooltip, {
|
||||
date: new Date(version.date_published),
|
||||
time: new Date(version.date_published),
|
||||
})
|
||||
"
|
||||
v-tooltip="formatDateTime(version.date_published)"
|
||||
class="z-[1] flex cursor-help items-center gap-1 text-nowrap font-medium xl:self-center"
|
||||
>
|
||||
<CalendarIcon class="xl:hidden" />
|
||||
@@ -186,7 +181,7 @@
|
||||
class="pointer-events-none z-[1] flex items-center gap-1 font-medium xl:self-center"
|
||||
>
|
||||
<DownloadIcon class="xl:hidden" />
|
||||
{{ formatNumber(version.downloads) }}
|
||||
{{ formatCompactNumber(version.downloads) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -227,12 +222,13 @@ import {
|
||||
FormattedTag,
|
||||
Pagination,
|
||||
TagItem,
|
||||
useCompactNumber,
|
||||
useFormatDateTime,
|
||||
VersionChannelIndicator,
|
||||
VersionFilterControl,
|
||||
} from '@modrinth/ui'
|
||||
import {
|
||||
formatBytes,
|
||||
formatNumber,
|
||||
formatVersionsForDisplay,
|
||||
type GameVersionTag,
|
||||
type Version,
|
||||
@@ -242,11 +238,15 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { useRelativeTime } from '../../composables'
|
||||
import { useVIntl } from '../../composables/i18n'
|
||||
import { commonMessages } from '../../utils/common-messages'
|
||||
import { getEnvironmentTags } from './settings/environment/environments'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const { formatCompactNumber } = useCompactNumber()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
type VersionWithDisplayUrlEnding = Version & {
|
||||
displayUrlEnding: string
|
||||
|
||||
@@ -36,10 +36,7 @@
|
||||
{{ formatMessage(commonMessages.projectFollowers, { count: project.followers }) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="project.approved"
|
||||
v-tooltip="dayjs(project.approved).format('MMMM D, YYYY [at] h:mm A')"
|
||||
>
|
||||
<div v-if="project.approved" v-tooltip="formatDateTime(project.approved)">
|
||||
<CalendarIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{
|
||||
@@ -49,7 +46,7 @@
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else v-tooltip="dayjs(project.published).format('MMMM D, YYYY [at] h:mm A')">
|
||||
<div v-else v-tooltip="formatDateTime(project.published)">
|
||||
<CalendarIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{
|
||||
@@ -59,7 +56,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="project.status === 'processing' && project.queued"
|
||||
v-tooltip="dayjs(project.queued).format('MMMM D, YYYY [at] h:mm A')"
|
||||
v-tooltip="formatDateTime(project.queued)"
|
||||
>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
<div>
|
||||
@@ -70,10 +67,7 @@
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="hasVersions && project.updated"
|
||||
v-tooltip="dayjs(project.updated).format('MMMM D, YYYY [at] h:mm A')"
|
||||
>
|
||||
<div v-if="hasVersions && project.updated" v-tooltip="formatDateTime(project.updated)">
|
||||
<VersionIcon aria-hidden="true" />
|
||||
<div>
|
||||
{{
|
||||
@@ -95,16 +89,19 @@ import {
|
||||
VersionIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { capitalizeString } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useRelativeTime } from '../../composables'
|
||||
import { useFormatDateTime, useRelativeTime } from '../../composables'
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import { commonMessages } from '../../utils/common-messages'
|
||||
import { IntlFormatted } from '../base'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
project: Labrinth.Projects.v2.Project
|
||||
|
||||
@@ -1,36 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { CalendarIcon, HistoryIcon } from '@modrinth/assets'
|
||||
import { capitalizeString } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useRelativeTime, useVIntl } from '../../../composables'
|
||||
import { defineMessage, useFormatDateTime, useRelativeTime, useVIntl } from '../../../composables'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
date: Date
|
||||
type: 'updated' | 'published'
|
||||
}>()
|
||||
|
||||
const formattedDate = computed(() => dayjs(props.date).format('MMMM D, YYYY [at] h:mm A'))
|
||||
const formattedDate = computed(() => formatDateTime(props.date))
|
||||
|
||||
const types = {
|
||||
updated: {
|
||||
icon: HistoryIcon,
|
||||
tooltip: {
|
||||
tooltip: defineMessage({
|
||||
id: 'project-card.date.updated.tooltip',
|
||||
defaultMessage: 'Updated {date}',
|
||||
},
|
||||
}),
|
||||
},
|
||||
published: {
|
||||
icon: CalendarIcon,
|
||||
tooltip: {
|
||||
tooltip: defineMessage({
|
||||
id: 'project-card.date.published.tooltip',
|
||||
defaultMessage: 'Published {date}',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon, HeartIcon } from '@modrinth/assets'
|
||||
|
||||
import { capitalizeString, formatNumber } from '../../../../../utils'
|
||||
import { useVIntl } from '../../../composables'
|
||||
import { capitalizeString } from '../../../../../utils'
|
||||
import { useCompactNumber, useVIntl } from '../../../composables'
|
||||
import { commonMessages } from '../../../utils'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { formatCompactNumber } = useCompactNumber()
|
||||
|
||||
defineProps<{
|
||||
downloads?: number
|
||||
@@ -19,7 +20,7 @@ defineProps<{
|
||||
v-tooltip="
|
||||
capitalizeString(
|
||||
formatMessage(commonMessages.projectDownloads, {
|
||||
count: formatNumber(downloads, false),
|
||||
count: downloads,
|
||||
}),
|
||||
)
|
||||
"
|
||||
@@ -27,7 +28,7 @@ defineProps<{
|
||||
>
|
||||
<DownloadIcon class="size-5 shrink-0" />
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(downloads) }}
|
||||
{{ formatCompactNumber(downloads) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@@ -35,7 +36,7 @@ defineProps<{
|
||||
v-tooltip="
|
||||
capitalizeString(
|
||||
formatMessage(commonMessages.projectFollowers, {
|
||||
count: formatNumber(followers, false),
|
||||
count: followers,
|
||||
}),
|
||||
)
|
||||
"
|
||||
@@ -43,7 +44,7 @@ defineProps<{
|
||||
>
|
||||
<HeartIcon class="size-5 shrink-0" />
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(followers) }}
|
||||
{{ formatCompactNumber(followers) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { OnlineIndicatorIcon } from '@modrinth/assets'
|
||||
|
||||
import { formatNumber } from '../../../../../utils'
|
||||
import { useVIntl } from '../../../composables'
|
||||
import { useCompactNumber, useFormatNumber, useVIntl } from '../../../composables'
|
||||
import { commonMessages } from '../../../utils'
|
||||
import { StatItem } from '../../base'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
|
||||
const formatNumber = useFormatNumber()
|
||||
|
||||
defineProps<{
|
||||
online: number
|
||||
@@ -16,7 +17,12 @@ defineProps<{
|
||||
</script>
|
||||
<template>
|
||||
<StatItem
|
||||
v-tooltip="`${formatNumber(online, true)} players online`"
|
||||
v-tooltip="
|
||||
formatMessage(commonMessages.projectOnlinePlayerCountTooltip, {
|
||||
count: formatCompactNumber(online),
|
||||
countPlural: formatCompactNumberPlural(online),
|
||||
})
|
||||
"
|
||||
class="smart-clickable:allow-pointer-events w-max"
|
||||
>
|
||||
<OnlineIndicatorIcon
|
||||
@@ -29,10 +35,8 @@ defineProps<{
|
||||
/>
|
||||
{{
|
||||
hideLabel
|
||||
? formatNumber(online, false)
|
||||
: formatMessage(commonMessages.projectOnlinePlayerCount, {
|
||||
count: formatNumber(online, false),
|
||||
})
|
||||
? formatNumber(online)
|
||||
: formatMessage(commonMessages.projectOnlinePlayerCount, { count: online })
|
||||
}}
|
||||
</StatItem>
|
||||
</template>
|
||||
|
||||
@@ -12,7 +12,7 @@ const props = defineProps<{
|
||||
|
||||
const pingMessage = defineMessage({
|
||||
id: 'project.server.ping.ms',
|
||||
defaultMessage: '{ping} ms',
|
||||
defaultMessage: '{ping, number} ms',
|
||||
})
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { PlayIcon } from '@modrinth/assets'
|
||||
|
||||
import { formatNumber } from '../../../../../utils'
|
||||
import { useVIntl } from '../../../composables'
|
||||
import { useCompactNumber, useVIntl } from '../../../composables'
|
||||
import { commonMessages } from '../../../utils'
|
||||
import { StatItem } from '../../base'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
|
||||
|
||||
defineProps<{
|
||||
recentPlays: number
|
||||
@@ -16,16 +16,20 @@ defineProps<{
|
||||
<template>
|
||||
<StatItem
|
||||
v-tooltip="
|
||||
`${formatNumber(recentPlays, true)} recent play${recentPlays === 1 ? '' : 's'} from Modrinth in the past 2 weeks`
|
||||
formatMessage(commonMessages.projectRecentPlaysTooltip, {
|
||||
count: formatCompactNumber(recentPlays),
|
||||
countPlural: formatCompactNumberPlural(recentPlays),
|
||||
})
|
||||
"
|
||||
class="smart-clickable:allow-pointer-events w-max"
|
||||
>
|
||||
<PlayIcon />
|
||||
{{
|
||||
hideLabel
|
||||
? formatNumber(recentPlays, true)
|
||||
? formatCompactNumber(recentPlays)
|
||||
: formatMessage(commonMessages.projectRecentPlays, {
|
||||
count: formatNumber(recentPlays, true),
|
||||
count: formatCompactNumber(recentPlays),
|
||||
countPlural: formatCompactNumberPlural(recentPlays),
|
||||
})
|
||||
}}
|
||||
</StatItem>
|
||||
|
||||
@@ -122,9 +122,9 @@ import {
|
||||
TriangleAlertIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useFormatDateTime } from '../../composables'
|
||||
import { injectModrinthClient } from '../../providers/api-client'
|
||||
import Avatar from '../base/Avatar.vue'
|
||||
import CopyCode from '../base/CopyCode.vue'
|
||||
@@ -132,6 +132,8 @@ import ServersSpecs from '../billing/ServersSpecs.vue'
|
||||
import ServerIcon from './icons/ServerIcon.vue'
|
||||
import ServerInfoLabels from './labels/ServerInfoLabels.vue'
|
||||
|
||||
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||
|
||||
export type PendingChange = {
|
||||
planSize: string
|
||||
cpu: number
|
||||
@@ -249,12 +251,4 @@ const { data: image } = useQuery({
|
||||
})
|
||||
|
||||
const isConfiguring = computed(() => props.flows?.intro)
|
||||
|
||||
const formatDate = (d: unknown) => {
|
||||
try {
|
||||
return dayjs(d as string).format('MMMM D, YYYY')
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
UserRoundIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useFormatDateTime } from '../../../composables'
|
||||
import { defineMessages, useVIntl } from '../../../composables/i18n'
|
||||
import { commonMessages } from '../../../utils'
|
||||
import ButtonStyled from '../../base/ButtonStyled.vue'
|
||||
@@ -20,6 +20,10 @@ import OverflowMenu, { type Option as OverflowOption } from '../../base/Overflow
|
||||
import ProgressBar from '../../base/ProgressBar.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'download' | 'rename' | 'restore' | 'retry'): void
|
||||
@@ -254,7 +258,7 @@ const messages = defineMessages({
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="w-full font-medium text-contrast md:text-center">
|
||||
{{ dayjs(backup.created_at).format('MMMM Do YYYY, h:mm A') }}
|
||||
{{ formatDateTime(backup.created_at) }}
|
||||
</span>
|
||||
<!-- TODO: Uncomment when API supports size field -->
|
||||
<!-- <span class="text-secondary">{{ formatBytes(backup.size) }}</span> -->
|
||||
|
||||
@@ -81,6 +81,7 @@ import {
|
||||
getFileExtensionIcon,
|
||||
isEditableFile as isEditableFileExt,
|
||||
isImageFile,
|
||||
useFormatDateTime,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
@@ -123,6 +124,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.selected ? 'bg-surface-3' : props.index % 2 === 0 ? 'bg-surface-2' : 'file-row-alt',
|
||||
@@ -179,28 +188,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(() => {
|
||||
|
||||
Reference in New Issue
Block a user