fix: #321 use date-fns for localized datetime formatting (#345)

* fix: #321 use date-fns for localized datetime formatting

* chore: lint fixes for use-formatters

* chore: more lint fixes for use-formatters

* date and currency localization fixes

---------

Co-authored-by: Ádám Kleizer <adkl@boyum-it.com>
This commit is contained in:
Adam Kleizer
2024-11-23 18:33:46 +01:00
committed by GitHub
parent 6662bbd5b9
commit 3d972dcac3
7 changed files with 77 additions and 97 deletions

View File

@@ -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<UseTimeAgoUnitNamesDefault> = {
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);
}