mirror of
https://github.com/umami-software/umami.git
synced 2026-02-20 20:45:39 +01:00
i18n datetime
This commit is contained in:
parent
338c2f3071
commit
4c3f3cbc71
9 changed files with 57 additions and 55 deletions
|
|
@ -2,15 +2,15 @@ import { useContext } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
import { ReportContext } from '../[reportId]/Report';
|
||||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
import { useMessages, useLocale } from 'components/hooks';
|
import { useMessages } from 'components/hooks';
|
||||||
import { formatDate } from 'lib/date';
|
|
||||||
import styles from './RetentionTable.module.css';
|
import styles from './RetentionTable.module.css';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
const DAYS = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28];
|
const DAYS = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28];
|
||||||
|
|
||||||
export function RetentionTable({ days = DAYS }) {
|
export function RetentionTable({ days = DAYS }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale } = useLocale();
|
const intl = useIntl();
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useContext(ReportContext);
|
||||||
const { data } = report || {};
|
const { data } = report || {};
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ export function RetentionTable({ days = DAYS }) {
|
||||||
{rows.map(({ date, visitors, records }, rowIndex) => {
|
{rows.map(({ date, visitors, records }, rowIndex) => {
|
||||||
return (
|
return (
|
||||||
<div key={rowIndex} className={styles.row}>
|
<div key={rowIndex} className={styles.row}>
|
||||||
<div className={styles.date}>{formatDate(date, 'PP', locale)}</div>
|
<div className={styles.date}>{intl.formatDate(date, { dateStyle: 'medium' })}</div>
|
||||||
<div className={styles.visitors}>{visitors}</div>
|
<div className={styles.visitors}>{visitors}</div>
|
||||||
{days.map(day => {
|
{days.map(day => {
|
||||||
if (totalDays - rowIndex < day) {
|
if (totalDays - rowIndex < day) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { renderDateLabels } from 'lib/charts';
|
||||||
import { formatLongNumber } from 'lib/format';
|
import { formatLongNumber } from 'lib/format';
|
||||||
import { useContext, useMemo } from 'react';
|
import { useContext, useMemo } from 'react';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
import { ReportContext } from '../[reportId]/Report';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
export interface PageviewsChartProps extends BarChartProps {
|
export interface PageviewsChartProps extends BarChartProps {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
|
@ -14,6 +15,7 @@ export interface PageviewsChartProps extends BarChartProps {
|
||||||
export function RevenueChart({ isLoading, ...props }: PageviewsChartProps) {
|
export function RevenueChart({ isLoading, ...props }: PageviewsChartProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
|
const intl = useIntl();
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useContext(ReportContext);
|
||||||
const { data, parameters } = report || {};
|
const { data, parameters } = report || {};
|
||||||
|
|
||||||
|
|
@ -88,7 +90,7 @@ export function RevenueChart({ isLoading, ...props }: PageviewsChartProps) {
|
||||||
data={chartData}
|
data={chartData}
|
||||||
unit={parameters?.dateRange.unit}
|
unit={parameters?.dateRange.unit}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
renderXLabel={renderDateLabels(parameters?.dateRange.unit, locale)}
|
renderXLabel={renderDateLabels(intl, parameters?.dateRange.unit)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,34 @@
|
||||||
import { formatDate } from 'lib/date';
|
|
||||||
import { Flexbox, StatusLight } from 'react-basics';
|
import { Flexbox, StatusLight } from 'react-basics';
|
||||||
import { formatLongNumber } from 'lib/format';
|
import { formatLongNumber } from 'lib/format';
|
||||||
import { useLocale } from 'components/hooks';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
const formats = {
|
const formats = {
|
||||||
millisecond: 'T',
|
millisecond: {
|
||||||
second: 'pp',
|
year: 'numeric',
|
||||||
minute: 'p',
|
month: 'numeric',
|
||||||
hour: 'h:mm aaa - PP',
|
day: 'numeric',
|
||||||
day: 'PPPP',
|
hour: 'numeric',
|
||||||
week: 'PPPP',
|
minute: 'numeric',
|
||||||
month: 'LLLL yyyy',
|
second: 'numeric',
|
||||||
quarter: 'qqq',
|
fractionalSecondDigits: 3,
|
||||||
year: 'yyyy',
|
},
|
||||||
|
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 }) {
|
export default function BarChartTooltip({ tooltip, unit }) {
|
||||||
const { locale } = useLocale();
|
const intl = useIntl();
|
||||||
const { labelColors, dataPoints } = tooltip;
|
const { labelColors, dataPoints } = tooltip;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flexbox direction="column" gap={10}>
|
<Flexbox direction="column" gap={10}>
|
||||||
<div>
|
<div>{intl.formatDate(dataPoints[0].raw.d || dataPoints[0].raw.x, formats[unit])}</div>
|
||||||
{formatDate(new Date(dataPoints[0].raw.d || dataPoints[0].raw.x), formats[unit], locale)}
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<StatusLight color={labelColors?.[0]?.backgroundColor}>
|
<StatusLight color={labelColors?.[0]?.backgroundColor}>
|
||||||
{formatLongNumber(dataPoints[0].raw.y)} {dataPoints[0].dataset.label}
|
{formatLongNumber(dataPoints[0].raw.y)} {dataPoints[0].dataset.label}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import { useState } from 'react';
|
||||||
import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics';
|
import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics';
|
||||||
import { endOfYear, isSameDay } from 'date-fns';
|
import { endOfYear, isSameDay } from 'date-fns';
|
||||||
import DatePickerForm from 'components/metrics/DatePickerForm';
|
import DatePickerForm from 'components/metrics/DatePickerForm';
|
||||||
import { useLocale, useMessages } from 'components/hooks';
|
import { useMessages } from 'components/hooks';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import { formatDate, parseDateValue } from 'lib/date';
|
import { parseDateValue } from 'lib/date';
|
||||||
import styles from './DateFilter.module.css';
|
import styles from './DateFilter.module.css';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
export interface DateFilterProps {
|
export interface DateFilterProps {
|
||||||
value: string;
|
value: string;
|
||||||
|
|
@ -31,7 +32,7 @@ export function DateFilter({
|
||||||
}: DateFilterProps) {
|
}: DateFilterProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [showPicker, setShowPicker] = useState(false);
|
const [showPicker, setShowPicker] = useState(false);
|
||||||
const { locale } = useLocale();
|
const intl = useIntl();
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{ label: formatMessage(labels.today), value: '0day' },
|
{ label: formatMessage(labels.today), value: '0day' },
|
||||||
|
|
@ -101,11 +102,11 @@ export function DateFilter({
|
||||||
const { unit } = parseDateValue(value) || {};
|
const { unit } = parseDateValue(value) || {};
|
||||||
|
|
||||||
if (offset && unit === 'year') {
|
if (offset && unit === 'year') {
|
||||||
return formatDate(startDate, 'yyyy', locale);
|
return intl.formatDate(startDate, { year: 'numeric' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset && unit === 'month') {
|
if (offset && unit === 'month') {
|
||||||
return formatDate(startDate, 'MMMM yyyy', locale);
|
return intl.formatDate(startDate, { year: 'numeric', month: 'long' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.startsWith('range') || offset) {
|
if (value.startsWith('range') || offset) {
|
||||||
|
|
@ -156,7 +157,7 @@ export function DateFilter({
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomRange = ({ startDate, endDate, unit, onClick }) => {
|
const CustomRange = ({ startDate, endDate, unit, onClick }) => {
|
||||||
const { locale } = useLocale();
|
const intl = useIntl();
|
||||||
|
|
||||||
const monthFormat = unit === 'month';
|
const monthFormat = unit === 'month';
|
||||||
|
|
||||||
|
|
@ -173,11 +174,12 @@ const CustomRange = ({ startDate, endDate, unit, onClick }) => {
|
||||||
</Icon>
|
</Icon>
|
||||||
<Text>
|
<Text>
|
||||||
{monthFormat ? (
|
{monthFormat ? (
|
||||||
<>{formatDate(startDate, 'MMMM yyyy', locale)}</>
|
<>{intl.formatDate(startDate, { year: 'numeric', month: 'long' })}</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{formatDate(startDate, 'd LLL y', locale)}
|
{intl.formatDate(startDate, { dateStyle: 'medium' })}
|
||||||
{!isSameDay(startDate, endDate) && ` — ${formatDate(endDate, 'd LLL y', locale)}`}
|
{!isSameDay(startDate, endDate) &&
|
||||||
|
` — ${intl.formatDate(endDate, { dateStyle: 'medium' })}`}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@ import {
|
||||||
import { startOfMonth, endOfMonth } from 'date-fns';
|
import { startOfMonth, endOfMonth } from 'date-fns';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import { useLocale } from 'components/hooks';
|
import { useLocale } from 'components/hooks';
|
||||||
import { formatDate } from 'lib/date';
|
|
||||||
import styles from './MonthSelect.module.css';
|
import styles from './MonthSelect.module.css';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
export function MonthSelect({ date = new Date(), onChange }) {
|
export function MonthSelect({ date = new Date(), onChange }) {
|
||||||
const { locale, dateLocale } = useLocale();
|
const { dateLocale } = useLocale();
|
||||||
const month = formatDate(date, 'MMMM', locale);
|
const intl = useIntl();
|
||||||
const year = date.getFullYear();
|
const month = intl.formatDate(date, { month: 'numeric' });
|
||||||
|
const year = intl.formatDate(date, { year: 'numeric' });
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
|
|
||||||
const handleChange = (close: () => void, date: Date) => {
|
const handleChange = (close: () => void, date: Date) => {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { colord } from 'colord';
|
import { colord } from 'colord';
|
||||||
import BarChart from 'components/charts/BarChart';
|
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 { CHART_COLORS } from 'lib/constants';
|
||||||
import { renderDateLabels } from 'lib/charts';
|
import { renderDateLabels } from 'lib/charts';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
export interface EventsChartProps {
|
export interface EventsChartProps {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
|
|
@ -14,8 +15,8 @@ export function EventsChart({ websiteId, className }: EventsChartProps) {
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange(websiteId);
|
} = useDateRange(websiteId);
|
||||||
const { locale } = useLocale();
|
|
||||||
const { data, isLoading } = useWebsiteEventsSeries(websiteId);
|
const { data, isLoading } = useWebsiteEventsSeries(websiteId);
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
if (!data) return [];
|
if (!data) return [];
|
||||||
|
|
@ -51,7 +52,7 @@ export function EventsChart({ websiteId, className }: EventsChartProps) {
|
||||||
data={chartData}
|
data={chartData}
|
||||||
unit={unit}
|
unit={unit}
|
||||||
stacked={true}
|
stacked={true}
|
||||||
renderXLabel={renderDateLabels(unit, locale)}
|
renderXLabel={renderDateLabels(intl, unit)}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useMemo } from 'react';
|
||||||
import BarChart, { BarChartProps } from 'components/charts/BarChart';
|
import BarChart, { BarChartProps } from 'components/charts/BarChart';
|
||||||
import { useLocale, useTheme, useMessages } from 'components/hooks';
|
import { useLocale, useTheme, useMessages } from 'components/hooks';
|
||||||
import { renderDateLabels } from 'lib/charts';
|
import { renderDateLabels } from 'lib/charts';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
export interface PagepageviewsChartProps extends BarChartProps {
|
export interface PagepageviewsChartProps extends BarChartProps {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -20,6 +21,7 @@ export function PagepageviewsChart({ data, unit, isLoading, ...props }: Pagepage
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|
@ -74,7 +76,7 @@ export function PagepageviewsChart({ data, unit, isLoading, ...props }: Pagepage
|
||||||
data={chartData}
|
data={chartData}
|
||||||
unit={unit}
|
unit={unit}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
renderXLabel={renderDateLabels(unit, locale)}
|
renderXLabel={renderDateLabels(intl, unit)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
import { formatDate } from 'lib/date';
|
|
||||||
import { formatLongNumber } from 'lib/format';
|
import { formatLongNumber } from 'lib/format';
|
||||||
|
import { type IntlShape } from 'react-intl';
|
||||||
|
|
||||||
export function renderNumberLabels(label: string) {
|
export function renderNumberLabels(label: string) {
|
||||||
return +label > 1000 ? formatLongNumber(+label) : label;
|
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[]) => {
|
return (label: string, index: number, values: any[]) => {
|
||||||
const d = new Date(values[index].value);
|
const d = new Date(values[index].value);
|
||||||
|
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case 'minute':
|
case 'minute':
|
||||||
return formatDate(d, 'h:mm', locale);
|
return intl.formatDate(d, { timeStyle: 'short' });
|
||||||
case 'hour':
|
case 'hour':
|
||||||
return formatDate(d, 'p', locale);
|
return intl.formatDate(d, { timeStyle: 'short' });
|
||||||
case 'day':
|
case 'day':
|
||||||
return formatDate(d, 'MMM d', locale);
|
return intl.formatDate(d, { month: 'short', day: 'numeric' });
|
||||||
case 'month':
|
case 'month':
|
||||||
return formatDate(d, 'MMM', locale);
|
return intl.formatDate(d, { month: 'short' });
|
||||||
case 'year':
|
case 'year':
|
||||||
return formatDate(d, 'YYY', locale);
|
return intl.formatDate(d, { year: 'numeric' });
|
||||||
default:
|
default:
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import {
|
||||||
differenceInCalendarWeeks,
|
differenceInCalendarWeeks,
|
||||||
differenceInCalendarMonths,
|
differenceInCalendarMonths,
|
||||||
differenceInCalendarYears,
|
differenceInCalendarYears,
|
||||||
format,
|
|
||||||
max,
|
max,
|
||||||
min,
|
min,
|
||||||
isDate,
|
isDate,
|
||||||
|
|
@ -288,16 +287,6 @@ export function getDateArray(data: any[], startDate: Date, endDate: Date, unit:
|
||||||
return arr;
|
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[]) {
|
export function maxDate(...args: Date[]) {
|
||||||
return max(args.filter(n => isDate(n)));
|
return max(args.filter(n => isDate(n)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue