diff --git a/frontend/components/Form/DatePicker.vue b/frontend/components/Form/DatePicker.vue index 725a6074..7422b603 100644 --- a/frontend/components/Form/DatePicker.vue +++ b/frontend/components/Form/DatePicker.vue @@ -3,13 +3,27 @@ - +
- +
@@ -38,6 +52,8 @@ const isDark = useIsDark(); + const formatDate = (date: Date | string | number) => fmtDate(date, "human", "date"); + const selected = computed({ get() { // String diff --git a/frontend/components/global/DateTime.vue b/frontend/components/global/DateTime.vue index b23cac2b..b82cf5c6 100644 --- a/frontend/components/global/DateTime.vue +++ b/frontend/components/global/DateTime.vue @@ -22,6 +22,6 @@ return ""; } - return fmtDate(props.date, props.format); + return fmtDate(props.date, props.format, props.datetimeType); }); diff --git a/frontend/composables/use-formatters.ts b/frontend/composables/use-formatters.ts index 602a7108..91cf9bf1 100644 --- a/frontend/composables/use-formatters.ts +++ b/frontend/composables/use-formatters.ts @@ -1,5 +1,6 @@ -import { useI18n } from "vue-i18n"; -import { type UseTimeAgoMessages, type UseTimeAgoUnitNamesDefault } from "@vueuse/core"; +import { format, formatDistance } from "date-fns"; +/* eslint import/namespace: ['error', { allowComputed: true }] */ +import * as Locales from "date-fns/locale"; const cache = { currency: "", @@ -20,105 +21,63 @@ export async function useFormatCurrency() { } } - return (value: number | string) => fmtCurrency(value, cache.currency); + return (value: number | string) => fmtCurrency(value, cache.currency, getLocaleCode()); } export type DateTimeFormat = "relative" | "long" | "short" | "human"; export type DateTimeType = "date" | "time" | "datetime"; -function ordinalIndicator(num: number) { - if (num > 3 && num < 21) return "th"; - switch (num % 10) { - case 1: - return "st"; - case 2: - return "nd"; - case 3: - return "rd"; - default: - return "th"; - } +export function getLocaleCode() { + const { $i18nGlobal } = useNuxtApp(); + return ($i18nGlobal?.locale?.value as string) ?? "en-US"; } -export function useLocaleTimeAgo(date: Date) { - const { t } = useI18n(); - - const I18N_MESSAGES: UseTimeAgoMessages = { - justNow: t("components.global.date_time.just-now"), - past: n => (n.match(/\d/) ? t("components.global.date_time.ago", [n]) : n), - future: n => (n.match(/\d/) ? t("components.global.date_time.in", [n]) : n), - month: (n, past) => - n === 1 - ? past - ? t("components.global.date_time.last-month") - : t("components.global.date_time.next-month") - : `${n} ${t(`components.global.date_time.months`)}`, - year: (n, past) => - n === 1 - ? past - ? t("components.global.date_time.last-year") - : t("components.global.date_time.next-year") - : `${n} ${t(`components.global.date_time.years`)}`, - day: (n, past) => - n === 1 - ? past - ? t("components.global.date_time.yesterday") - : t("components.global.date_time.tomorrow") - : `${n} ${t(`components.global.date_time.days`)}`, - week: (n, past) => - n === 1 - ? past - ? t("components.global.date_time.last-week") - : t("components.global.date_time.next-week") - : `${n} ${t(`components.global.date_time.weeks`)}`, - hour: n => `${n} ${n === 1 ? t("components.global.date_time.hour") : t("components.global.date_time.hours")}`, - minute: n => `${n} ${n === 1 ? t("components.global.date_time.minute") : t("components.global.date_time.minutes")}`, - second: n => `${n} ${n === 1 ? t("components.global.date_time.second") : t("components.global.date_time.seconds")}`, - invalid: "", - }; - - return useTimeAgo(date, { - fullDateFormatter: (date: Date) => date.toLocaleDateString(), - messages: I18N_MESSAGES, - }); +function getLocaleForDate() { + const localeCode = getLocaleCode(); + const lang = localeCode.length > 1 ? localeCode.substring(0, 2) : localeCode; + const region = localeCode.length > 2 ? localeCode.substring(3) : ""; + return Locales[(lang + region) as keyof typeof Locales] ?? Locales[lang as keyof typeof Locales] ?? Locales.enUS; } -export function fmtDate(value: string | Date, fmt: DateTimeFormat = "human"): string { - const months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; +export function fmtDate( + value: string | Date | number, + fmt: DateTimeFormat = "human", + type: DateTimeType = "date" +): string { + const dt = typeof value === "string" || typeof value === "number" ? new Date(value) : value; - const dt = typeof value === "string" ? new Date(value) : value; - if (!dt) { + if (!dt || !validDate(dt)) { return ""; } - if (!validDate(dt)) { - return ""; + const localeOptions = { locale: getLocaleForDate() }; + + if (fmt === "relative") { + return `${formatDistance(dt, new Date(), { ...localeOptions, addSuffix: true })} (${fmtDate(dt, "short", "date")})`; } + if (type === "time") { + return format(dt, "p", localeOptions); + } + + let formatStr = ""; + switch (fmt) { - case "relative": - return useLocaleTimeAgo(dt).value + useDateFormat(dt, " (YYYY-MM-DD)").value; - case "long": - return useDateFormat(dt, "YYYY-MM-DD (dddd)").value; - case "short": - return useDateFormat(dt, "YYYY-MM-DD").value; case "human": - // January 1st, 2021 - return `${months[dt.getMonth()]} ${dt.getDate()}${ordinalIndicator(dt.getDate())}, ${dt.getFullYear()}`; + formatStr = "PPP"; + break; + case "long": + formatStr = "PP"; + break; + case "short": + formatStr = "P"; + break; default: return ""; } + if (type === "datetime") { + formatStr += "p"; + } + + return format(dt, formatStr, localeOptions); } diff --git a/frontend/lib/datelib/datelib.ts b/frontend/lib/datelib/datelib.ts index c70dbf9e..b87922c1 100644 --- a/frontend/lib/datelib/datelib.ts +++ b/frontend/lib/datelib/datelib.ts @@ -11,7 +11,9 @@ export function format(date: Date | string): string { } export function zeroTime(date: Date): Date { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()); + return new Date( + new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - date.getTimezoneOffset() * 60000 + ); } export function factorRange(offset: number = 7): [Date, Date] { diff --git a/frontend/pages/item/[id]/index/edit.vue b/frontend/pages/item/[id]/index/edit.vue index a4c0b602..b86c9dc3 100644 --- a/frontend/pages/item/[id]/index/edit.vue +++ b/frontend/pages/item/[id]/index/edit.vue @@ -99,16 +99,12 @@ let purchasePrice = 0; let soldPrice = 0; - let purchaseTime = null; if (item.value.purchasePrice) { purchasePrice = item.value.purchasePrice; } if (item.value.soldPrice) { soldPrice = item.value.soldPrice; } - if (item.value.purchaseTime && typeof item.value.purchaseTime !== "string") { - purchaseTime = new Date(item.value.purchaseTime.getTime() - item.value.purchaseTime.getTimezoneOffset() * 60000); - } console.log((item.value.purchasePrice ??= 0)); console.log((item.value.soldPrice ??= 0)); @@ -121,7 +117,7 @@ assetId: item.value.assetId, purchasePrice, soldPrice, - purchaseTime: purchaseTime as Date, + purchaseTime: item.value.purchaseTime as Date, }; const { error } = await api.items.update(itemId.value, payload); diff --git a/frontend/pages/profile.vue b/frontend/pages/profile.vue index d13301b3..37eb503d 100644 --- a/frontend/pages/profile.vue +++ b/frontend/pages/profile.vue @@ -8,6 +8,7 @@ import MdiFill from "~icons/mdi/fill"; import MdiPencil from "~icons/mdi/pencil"; import MdiAccountMultiple from "~icons/mdi/account-multiple"; + import { getLocaleCode } from "~/composables/use-formatters"; definePageMeta({ middleware: ["auth"], @@ -52,12 +53,11 @@ }); const currencyExample = computed(() => { - const formatter = new Intl.NumberFormat("en-US", { - style: "currency", - currency: currency.value ? currency.value.code : "USD", - }); + return fmtCurrency(1000, currency.value?.code ?? "USD", getLocaleCode()); + }); - return formatter.format(1000); + const dateExample = computed(() => { + return fmtDate(new Date(Date.now() - 15 * 60000), "relative"); }); const { data: group } = useAsyncData(async () => { @@ -389,6 +389,7 @@ {{ $t(`languages.${lang}`) }} ({{ $t(`languages.${lang}`, 1, { locale: lang }) }}) +

{{ $t("profile.example") }}: {{ $t("global.created") }} {{ dateExample }}

diff --git a/frontend/plugins/i18n.ts b/frontend/plugins/i18n.ts index f6994dcb..f563fd13 100644 --- a/frontend/plugins/i18n.ts +++ b/frontend/plugins/i18n.ts @@ -30,6 +30,12 @@ export default defineNuxtPlugin(({ vueApp }) => { messages: messages(), }); vueApp.use(i18n); + + return { + provide: { + i18nGlobal: i18n.global, + }, + }; }); export const messages = () => {