From 4c3f3cbc7105fb59c13e570fbe5d206637d6e24f Mon Sep 17 00:00:00 2001 From: Minseo Lee Date: Wed, 28 Aug 2024 14:42:30 +0900 Subject: [PATCH] i18n datetime --- .../reports/retention/RetentionTable.tsx | 8 ++--- .../(main)/reports/revenue/RevenueChart.tsx | 4 ++- src/components/charts/BarChartTooltip.tsx | 35 +++++++++++-------- src/components/input/DateFilter.tsx | 20 ++++++----- src/components/input/MonthSelect.tsx | 9 ++--- src/components/metrics/EventsChart.tsx | 7 ++-- src/components/metrics/PageviewsChart.tsx | 4 ++- src/lib/charts.ts | 14 ++++---- src/lib/date.ts | 11 ------ 9 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/app/(main)/reports/retention/RetentionTable.tsx b/src/app/(main)/reports/retention/RetentionTable.tsx index 1770a7646..9f07022b6 100644 --- a/src/app/(main)/reports/retention/RetentionTable.tsx +++ b/src/app/(main)/reports/retention/RetentionTable.tsx @@ -2,15 +2,15 @@ import { useContext } from 'react'; import classNames from 'classnames'; import { ReportContext } from '../[reportId]/Report'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; -import { useMessages, useLocale } from 'components/hooks'; -import { formatDate } from 'lib/date'; +import { useMessages } from 'components/hooks'; import styles from './RetentionTable.module.css'; +import { useIntl } from 'react-intl'; const DAYS = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28]; export function RetentionTable({ days = DAYS }) { const { formatMessage, labels } = useMessages(); - const { locale } = useLocale(); + const intl = useIntl(); const { report } = useContext(ReportContext); const { data } = report || {}; @@ -52,7 +52,7 @@ export function RetentionTable({ days = DAYS }) { {rows.map(({ date, visitors, records }, rowIndex) => { return (
-
{formatDate(date, 'PP', locale)}
+
{intl.formatDate(date, { dateStyle: 'medium' })}
{visitors}
{days.map(day => { if (totalDays - rowIndex < day) { diff --git a/src/app/(main)/reports/revenue/RevenueChart.tsx b/src/app/(main)/reports/revenue/RevenueChart.tsx index 602ab31c5..aee6df6c4 100644 --- a/src/app/(main)/reports/revenue/RevenueChart.tsx +++ b/src/app/(main)/reports/revenue/RevenueChart.tsx @@ -6,6 +6,7 @@ import { renderDateLabels } from 'lib/charts'; import { formatLongNumber } from 'lib/format'; import { useContext, useMemo } from 'react'; import { ReportContext } from '../[reportId]/Report'; +import { useIntl } from 'react-intl'; export interface PageviewsChartProps extends BarChartProps { isLoading?: boolean; @@ -14,6 +15,7 @@ export interface PageviewsChartProps extends BarChartProps { export function RevenueChart({ isLoading, ...props }: PageviewsChartProps) { const { formatMessage, labels } = useMessages(); const { locale } = useLocale(); + const intl = useIntl(); const { report } = useContext(ReportContext); const { data, parameters } = report || {}; @@ -88,7 +90,7 @@ export function RevenueChart({ isLoading, ...props }: PageviewsChartProps) { data={chartData} unit={parameters?.dateRange.unit} isLoading={isLoading} - renderXLabel={renderDateLabels(parameters?.dateRange.unit, locale)} + renderXLabel={renderDateLabels(intl, parameters?.dateRange.unit)} /> )} diff --git a/src/components/charts/BarChartTooltip.tsx b/src/components/charts/BarChartTooltip.tsx index fed5af924..f9ce49757 100644 --- a/src/components/charts/BarChartTooltip.tsx +++ b/src/components/charts/BarChartTooltip.tsx @@ -1,29 +1,34 @@ -import { formatDate } from 'lib/date'; import { Flexbox, StatusLight } from 'react-basics'; import { formatLongNumber } from 'lib/format'; -import { useLocale } from 'components/hooks'; +import { useIntl } from 'react-intl'; const formats = { - millisecond: 'T', - second: 'pp', - minute: 'p', - hour: 'h:mm aaa - PP', - day: 'PPPP', - week: 'PPPP', - month: 'LLLL yyyy', - quarter: 'qqq', - year: 'yyyy', + millisecond: { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + fractionalSecondDigits: 3, + }, + second: { timeStyle: 'medium' }, + minute: { timeStyle: 'short' }, + hour: { dateStyle: 'medium', timeStyle: 'short' }, + day: { dateStyle: 'full' }, + week: { dateStyle: 'full' }, + month: { year: 'numeric', month: 'long' }, + quarter: { year: 'numeric', month: 'long' }, + year: { year: 'numeric' }, }; export default function BarChartTooltip({ tooltip, unit }) { - const { locale } = useLocale(); + const intl = useIntl(); const { labelColors, dataPoints } = tooltip; return ( -
- {formatDate(new Date(dataPoints[0].raw.d || dataPoints[0].raw.x), formats[unit], locale)} -
+
{intl.formatDate(dataPoints[0].raw.d || dataPoints[0].raw.x, formats[unit])}
{formatLongNumber(dataPoints[0].raw.y)} {dataPoints[0].dataset.label} diff --git a/src/components/input/DateFilter.tsx b/src/components/input/DateFilter.tsx index e486551d6..6e26c46bd 100644 --- a/src/components/input/DateFilter.tsx +++ b/src/components/input/DateFilter.tsx @@ -2,11 +2,12 @@ import { useState } from 'react'; import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics'; import { endOfYear, isSameDay } from 'date-fns'; import DatePickerForm from 'components/metrics/DatePickerForm'; -import { useLocale, useMessages } from 'components/hooks'; +import { useMessages } from 'components/hooks'; import Icons from 'components/icons'; -import { formatDate, parseDateValue } from 'lib/date'; +import { parseDateValue } from 'lib/date'; import styles from './DateFilter.module.css'; import classNames from 'classnames'; +import { useIntl } from 'react-intl'; export interface DateFilterProps { value: string; @@ -31,7 +32,7 @@ export function DateFilter({ }: DateFilterProps) { const { formatMessage, labels } = useMessages(); const [showPicker, setShowPicker] = useState(false); - const { locale } = useLocale(); + const intl = useIntl(); const options = [ { label: formatMessage(labels.today), value: '0day' }, @@ -101,11 +102,11 @@ export function DateFilter({ const { unit } = parseDateValue(value) || {}; if (offset && unit === 'year') { - return formatDate(startDate, 'yyyy', locale); + return intl.formatDate(startDate, { year: 'numeric' }); } if (offset && unit === 'month') { - return formatDate(startDate, 'MMMM yyyy', locale); + return intl.formatDate(startDate, { year: 'numeric', month: 'long' }); } if (value.startsWith('range') || offset) { @@ -156,7 +157,7 @@ export function DateFilter({ } const CustomRange = ({ startDate, endDate, unit, onClick }) => { - const { locale } = useLocale(); + const intl = useIntl(); const monthFormat = unit === 'month'; @@ -173,11 +174,12 @@ const CustomRange = ({ startDate, endDate, unit, onClick }) => { {monthFormat ? ( - <>{formatDate(startDate, 'MMMM yyyy', locale)} + <>{intl.formatDate(startDate, { year: 'numeric', month: 'long' })} ) : ( <> - {formatDate(startDate, 'd LLL y', locale)} - {!isSameDay(startDate, endDate) && ` — ${formatDate(endDate, 'd LLL y', locale)}`} + {intl.formatDate(startDate, { dateStyle: 'medium' })} + {!isSameDay(startDate, endDate) && + ` — ${intl.formatDate(endDate, { dateStyle: 'medium' })}`} )} diff --git a/src/components/input/MonthSelect.tsx b/src/components/input/MonthSelect.tsx index acb17dfe6..9556b8e7c 100644 --- a/src/components/input/MonthSelect.tsx +++ b/src/components/input/MonthSelect.tsx @@ -11,13 +11,14 @@ import { import { startOfMonth, endOfMonth } from 'date-fns'; import Icons from 'components/icons'; import { useLocale } from 'components/hooks'; -import { formatDate } from 'lib/date'; import styles from './MonthSelect.module.css'; +import { useIntl } from 'react-intl'; export function MonthSelect({ date = new Date(), onChange }) { - const { locale, dateLocale } = useLocale(); - const month = formatDate(date, 'MMMM', locale); - const year = date.getFullYear(); + const { dateLocale } = useLocale(); + const intl = useIntl(); + const month = intl.formatDate(date, { month: 'numeric' }); + const year = intl.formatDate(date, { year: 'numeric' }); const ref = useRef(); const handleChange = (close: () => void, date: Date) => { diff --git a/src/components/metrics/EventsChart.tsx b/src/components/metrics/EventsChart.tsx index ee7f866cb..aa543611c 100644 --- a/src/components/metrics/EventsChart.tsx +++ b/src/components/metrics/EventsChart.tsx @@ -1,9 +1,10 @@ import { useMemo } from 'react'; import { colord } from 'colord'; import BarChart from 'components/charts/BarChart'; -import { useLocale, useDateRange, useWebsiteEventsSeries } from 'components/hooks'; +import { useDateRange, useWebsiteEventsSeries } from 'components/hooks'; import { CHART_COLORS } from 'lib/constants'; import { renderDateLabels } from 'lib/charts'; +import { useIntl } from 'react-intl'; export interface EventsChartProps { websiteId: string; @@ -14,8 +15,8 @@ export function EventsChart({ websiteId, className }: EventsChartProps) { const { dateRange: { startDate, endDate, unit }, } = useDateRange(websiteId); - const { locale } = useLocale(); const { data, isLoading } = useWebsiteEventsSeries(websiteId); + const intl = useIntl(); const chartData = useMemo(() => { if (!data) return []; @@ -51,7 +52,7 @@ export function EventsChart({ websiteId, className }: EventsChartProps) { data={chartData} unit={unit} stacked={true} - renderXLabel={renderDateLabels(unit, locale)} + renderXLabel={renderDateLabels(intl, unit)} isLoading={isLoading} /> ); diff --git a/src/components/metrics/PageviewsChart.tsx b/src/components/metrics/PageviewsChart.tsx index 6ca030949..36371f291 100644 --- a/src/components/metrics/PageviewsChart.tsx +++ b/src/components/metrics/PageviewsChart.tsx @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import BarChart, { BarChartProps } from 'components/charts/BarChart'; import { useLocale, useTheme, useMessages } from 'components/hooks'; import { renderDateLabels } from 'lib/charts'; +import { useIntl } from 'react-intl'; export interface PagepageviewsChartProps extends BarChartProps { data: { @@ -20,6 +21,7 @@ export function PagepageviewsChart({ data, unit, isLoading, ...props }: Pagepage const { formatMessage, labels } = useMessages(); const { colors } = useTheme(); const { locale } = useLocale(); + const intl = useIntl(); const chartData = useMemo(() => { if (!data) { @@ -74,7 +76,7 @@ export function PagepageviewsChart({ data, unit, isLoading, ...props }: Pagepage data={chartData} unit={unit} isLoading={isLoading} - renderXLabel={renderDateLabels(unit, locale)} + renderXLabel={renderDateLabels(intl, unit)} /> ); } diff --git a/src/lib/charts.ts b/src/lib/charts.ts index 8939b3c1b..75037d11c 100644 --- a/src/lib/charts.ts +++ b/src/lib/charts.ts @@ -1,25 +1,25 @@ -import { formatDate } from 'lib/date'; import { formatLongNumber } from 'lib/format'; +import { type IntlShape } from 'react-intl'; export function renderNumberLabels(label: string) { return +label > 1000 ? formatLongNumber(+label) : label; } -export function renderDateLabels(unit: string, locale: string) { +export function renderDateLabels(intl: IntlShape, unit: string) { return (label: string, index: number, values: any[]) => { const d = new Date(values[index].value); switch (unit) { case 'minute': - return formatDate(d, 'h:mm', locale); + return intl.formatDate(d, { timeStyle: 'short' }); case 'hour': - return formatDate(d, 'p', locale); + return intl.formatDate(d, { timeStyle: 'short' }); case 'day': - return formatDate(d, 'MMM d', locale); + return intl.formatDate(d, { month: 'short', day: 'numeric' }); case 'month': - return formatDate(d, 'MMM', locale); + return intl.formatDate(d, { month: 'short' }); case 'year': - return formatDate(d, 'YYY', locale); + return intl.formatDate(d, { year: 'numeric' }); default: return label; } diff --git a/src/lib/date.ts b/src/lib/date.ts index b731140cf..332be5028 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -27,7 +27,6 @@ import { differenceInCalendarWeeks, differenceInCalendarMonths, differenceInCalendarYears, - format, max, min, isDate, @@ -288,16 +287,6 @@ export function getDateArray(data: any[], startDate: Date, endDate: Date, unit: return arr; } -export function formatDate(date: string | number | Date, str: string, locale = 'en-US') { - return format( - typeof date === 'string' ? new Date(date) : date, - CUSTOM_FORMATS?.[locale]?.[str] || str, - { - locale: getDateLocale(locale), - }, - ); -} - export function maxDate(...args: Date[]) { return max(args.filter(n => isDate(n))); }