mirror of
https://github.com/umami-software/umami.git
synced 2026-02-22 05:25:36 +01:00
Simplify i18n: remove old react-intl artifacts, rename formatMessage to t, replace FormattedMessage with t.rich().
- Rewrite messages.ts to plain string key maps (remove MessageDescriptor) - Rewrite useMessages hook to expose t from useTranslations() directly - Rename formatMessage → t across 193 consumer files - Replace custom FormattedMessage component with next-intl t.rich() - Update 52 language files to use rich text tags (<b>, <a>) - Remove all direct imports from @/components/messages in favor of useMessages() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
80cad6ea65
commit
50edb71687
247 changed files with 1660 additions and 2194 deletions
|
|
@ -37,7 +37,7 @@ export function Attribution({
|
|||
step,
|
||||
});
|
||||
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
const { pageviews, visitors, visits } = data?.total || {};
|
||||
|
||||
|
|
@ -45,17 +45,17 @@ export function Attribution({
|
|||
? [
|
||||
{
|
||||
value: visitors,
|
||||
label: formatMessage(labels.visitors),
|
||||
label: t(labels.visitors),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: visits,
|
||||
label: formatMessage(labels.visits),
|
||||
label: t(labels.visits),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: pageviews,
|
||||
label: formatMessage(labels.views),
|
||||
label: t(labels.views),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
]
|
||||
|
|
@ -72,7 +72,7 @@ export function Attribution({
|
|||
return (
|
||||
<ListTable
|
||||
title={title}
|
||||
metric={formatMessage(currency ? labels.revenue : labels.visitors)}
|
||||
metric={t(currency ? labels.revenue : labels.visitors)}
|
||||
currency={currency}
|
||||
data={attributionData.map(({ x, y, z }: { x: string; y: number; z: number }) => ({
|
||||
label: x,
|
||||
|
|
@ -94,31 +94,31 @@ export function Attribution({
|
|||
);
|
||||
})}
|
||||
</MetricsBar>
|
||||
<SectionHeader title={formatMessage(labels.sources)} />
|
||||
<SectionHeader title={t(labels.sources)} />
|
||||
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.referrer} title={formatMessage(labels.referrer)} />
|
||||
<AttributionTable data={data?.referrer} title={t(labels.referrer)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.paidAds} title={formatMessage(labels.paidAds)} />
|
||||
<AttributionTable data={data?.paidAds} title={t(labels.paidAds)} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
<SectionHeader title="UTM" />
|
||||
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_source} title={formatMessage(labels.sources)} />
|
||||
<AttributionTable data={data?.utm_source} title={t(labels.sources)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_medium} title={formatMessage(labels.medium)} />
|
||||
<AttributionTable data={data?.utm_medium} title={t(labels.medium)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_cmapaign} title={formatMessage(labels.campaigns)} />
|
||||
<AttributionTable data={data?.utm_cmapaign} title={t(labels.campaigns)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_content} title={formatMessage(labels.content)} />
|
||||
<AttributionTable data={data?.utm_content} title={t(labels.content)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_term} title={formatMessage(labels.terms)} />
|
||||
<AttributionTable data={data?.utm_term} title={t(labels.terms)} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
|
|||
const [model, setModel] = useState('first-click');
|
||||
const [type, setType] = useState('path');
|
||||
const [step, setStep] = useState('/');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange();
|
||||
|
|
@ -19,30 +19,20 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
|
|||
<WebsiteControls websiteId={websiteId} />
|
||||
<Grid columns={{ base: '1fr', md: '1fr 1fr 1fr' }} gap>
|
||||
<Column>
|
||||
<Select
|
||||
label={formatMessage(labels.model)}
|
||||
value={model}
|
||||
defaultValue={model}
|
||||
onChange={setModel}
|
||||
>
|
||||
<ListItem id="first-click">{formatMessage(labels.firstClick)}</ListItem>
|
||||
<ListItem id="last-click">{formatMessage(labels.lastClick)}</ListItem>
|
||||
<Select label={t(labels.model)} value={model} defaultValue={model} onChange={setModel}>
|
||||
<ListItem id="first-click">{t(labels.firstClick)}</ListItem>
|
||||
<ListItem id="last-click">{t(labels.lastClick)}</ListItem>
|
||||
</Select>
|
||||
</Column>
|
||||
<Column>
|
||||
<Select
|
||||
label={formatMessage(labels.type)}
|
||||
value={type}
|
||||
defaultValue={type}
|
||||
onChange={setType}
|
||||
>
|
||||
<ListItem id="path">{formatMessage(labels.viewedPage)}</ListItem>
|
||||
<ListItem id="event">{formatMessage(labels.triggeredEvent)}</ListItem>
|
||||
<Select label={t(labels.type)} value={type} defaultValue={type} onChange={setType}>
|
||||
<ListItem id="path">{t(labels.viewedPage)}</ListItem>
|
||||
<ListItem id="event">{t(labels.triggeredEvent)}</ListItem>
|
||||
</Select>
|
||||
</Column>
|
||||
<Column>
|
||||
<SearchField
|
||||
label={formatMessage(labels.conversionStep)}
|
||||
label={t(labels.conversionStep)}
|
||||
value={step}
|
||||
defaultValue={step}
|
||||
onSearch={setStep}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export interface BreakdownProps {
|
|||
}
|
||||
|
||||
export function Breakdown({ websiteId, selectedFields = [], startDate, endDate }: BreakdownProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { formatValue } = useFormat();
|
||||
const { fields } = useFields();
|
||||
const { data, error, isLoading } = useResultQuery<any>(
|
||||
|
|
@ -48,37 +48,22 @@ export function Breakdown({ websiteId, selectedFields = [], startDate, endDate }
|
|||
</DataColumn>
|
||||
);
|
||||
})}
|
||||
<DataColumn
|
||||
id="visitors"
|
||||
label={formatMessage(labels.visitors)}
|
||||
align="end"
|
||||
width="120px"
|
||||
>
|
||||
<DataColumn id="visitors" label={t(labels.visitors)} align="end" width="120px">
|
||||
{row => row?.visitors?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn id="visits" label={formatMessage(labels.visits)} align="end" width="120px">
|
||||
<DataColumn id="visits" label={t(labels.visits)} align="end" width="120px">
|
||||
{row => row?.visits?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn id="views" label={formatMessage(labels.views)} align="end" width="120px">
|
||||
<DataColumn id="views" label={t(labels.views)} align="end" width="120px">
|
||||
{row => row?.views?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn
|
||||
id="bounceRate"
|
||||
label={formatMessage(labels.bounceRate)}
|
||||
align="end"
|
||||
width="120px"
|
||||
>
|
||||
<DataColumn id="bounceRate" label={t(labels.bounceRate)} align="end" width="120px">
|
||||
{row => {
|
||||
const n = (Math.min(row?.visits, row?.bounces) / row?.visits) * 100;
|
||||
return `${Math.round(+n)}%`;
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn
|
||||
id="visitDuration"
|
||||
label={formatMessage(labels.visitDuration)}
|
||||
align="end"
|
||||
width="120px"
|
||||
>
|
||||
<DataColumn id="visitDuration" label={t(labels.visitDuration)} align="end" width="120px">
|
||||
{row => {
|
||||
const n = row?.totaltime / row?.visits;
|
||||
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
|||
}
|
||||
|
||||
const FieldsButton = ({ value, onChange }) => {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<ListCheck />}
|
||||
label={formatMessage(labels.fields)}
|
||||
label={t(labels.fields)}
|
||||
width="400px"
|
||||
minHeight="300px"
|
||||
variant="outline"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function FieldSelectForm({
|
|||
onClose?: () => void;
|
||||
}) {
|
||||
const [selected, setSelected] = useState(selectedFields);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { fields, groupLabels } = useFields();
|
||||
|
||||
const handleChange = (value: string[]) => {
|
||||
|
|
@ -57,9 +57,9 @@ export function FieldSelectForm({
|
|||
</List>
|
||||
</Column>
|
||||
<Grid columns="1fr 1fr" gap>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<Button onPress={onClose}>{t(labels.cancel)}</Button>
|
||||
<Button onPress={handleApply} variant="primary">
|
||||
{formatMessage(labels.apply)}
|
||||
{t(labels.apply)}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type FunnelResult = {
|
|||
};
|
||||
|
||||
export function Funnel({ id, name, type, parameters, websiteId }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { pathname } = useNavigation();
|
||||
const isSharePage = pathname.includes('/share/');
|
||||
const { data, error, isLoading } = useResultQuery(type, {
|
||||
|
|
@ -43,10 +43,7 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
|
|||
<ReportEditButton id={id} name={name} type={type}>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<Dialog
|
||||
title={formatMessage(labels.funnel)}
|
||||
style={{ minHeight: 300, minWidth: 400 }}
|
||||
>
|
||||
<Dialog title={t(labels.funnel)} style={{ minHeight: 300, minWidth: 400 }}>
|
||||
<FunnelEditForm id={id} websiteId={websiteId} onClose={close} />
|
||||
</Dialog>
|
||||
);
|
||||
|
|
@ -90,9 +87,9 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
|
|||
<Column gap>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Text color="muted">
|
||||
{formatMessage(isPage ? labels.viewedPage : labels.triggeredEvent)}
|
||||
{t(isPage ? labels.viewedPage : labels.triggeredEvent)}
|
||||
</Text>
|
||||
<Text color="muted">{formatMessage(labels.conversionRate)}</Text>
|
||||
<Text color="muted">{t(labels.conversionRate)}</Text>
|
||||
</Row>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Row alignItems="center" gap>
|
||||
|
|
@ -109,7 +106,7 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
|
|||
<User />
|
||||
</Icon>
|
||||
<Text title={visitors.toString()} transform="lowercase">
|
||||
{`${formatLongNumber(visitors)} ${formatMessage(labels.visitors)}`}
|
||||
{`${formatLongNumber(visitors)} ${t(labels.visitors)}`}
|
||||
</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Plus } from '@/components/icons';
|
|||
import { FunnelEditForm } from './FunnelEditForm';
|
||||
|
||||
export function FunnelAddButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
|
|
@ -12,14 +12,10 @@ export function FunnelAddButton({ websiteId }: { websiteId: string }) {
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.funnel)}</Text>
|
||||
<Text>{t(labels.funnel)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog
|
||||
variant="modal"
|
||||
title={formatMessage(labels.funnel)}
|
||||
style={{ minHeight: 375, minWidth: 600 }}
|
||||
>
|
||||
<Dialog variant="modal" title={t(labels.funnel)} style={{ minHeight: 375, minWidth: 600 }}>
|
||||
{({ close }) => <FunnelEditForm websiteId={websiteId} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function FunnelEditForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data } = useReportQuery(id);
|
||||
const { mutateAsync, error, isPending, touch } = useUpdateQuery(`/reports${id ? `/${id}` : ''}`);
|
||||
|
||||
|
|
@ -61,23 +61,15 @@ export function FunnelEditForm({
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error?.message} defaultValues={defaultValues}>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="name" label={t(labels.name)} rules={{ required: t(labels.required) }}>
|
||||
<TextField autoFocus />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="window"
|
||||
label={formatMessage(labels.window)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="window" label={t(labels.window)} rules={{ required: t(labels.required) }}>
|
||||
<TextField />
|
||||
</FormField>
|
||||
<FormFieldArray
|
||||
name="steps"
|
||||
label={formatMessage(labels.steps)}
|
||||
label={t(labels.steps)}
|
||||
rules={{
|
||||
validate: value => value.length > 1 || 'At least two steps are required',
|
||||
}}
|
||||
|
|
@ -91,7 +83,7 @@ export function FunnelEditForm({
|
|||
<Column>
|
||||
<FormField
|
||||
name={`steps.${index}.type`}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<ActionSelect />
|
||||
</FormField>
|
||||
|
|
@ -99,7 +91,7 @@ export function FunnelEditForm({
|
|||
<Column>
|
||||
<FormField
|
||||
name={`steps.${index}.value`}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
{({ field, context }) => {
|
||||
const type = context.watch(`steps.${index}.type`);
|
||||
|
|
@ -123,7 +115,7 @@ export function FunnelEditForm({
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.add)}</Text>
|
||||
<Text>{t(labels.add)}</Text>
|
||||
</Button>
|
||||
</Row>
|
||||
</Grid>
|
||||
|
|
@ -132,9 +124,9 @@ export function FunnelEditForm({
|
|||
</FormFieldArray>
|
||||
<FormButtons>
|
||||
<Button onPress={onClose} isDisabled={isPending}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
<FormSubmitButton>{t(labels.save)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export interface GoalProps {
|
|||
export type GoalData = { num: number; total: number };
|
||||
|
||||
export function Goal({ id, name, type, parameters, websiteId, startDate, endDate }: GoalProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { pathname } = useNavigation();
|
||||
const isSharePage = pathname.includes('/share/');
|
||||
const { data, error, isLoading, isFetching } = useResultQuery<GoalData>(type, {
|
||||
|
|
@ -53,7 +53,7 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
|
|||
{({ close }) => {
|
||||
return (
|
||||
<Dialog
|
||||
title={formatMessage(labels.goal)}
|
||||
title={t(labels.goal)}
|
||||
variant="modal"
|
||||
style={{ minHeight: 300, minWidth: 400 }}
|
||||
>
|
||||
|
|
@ -66,10 +66,8 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
|
|||
)}
|
||||
</Grid>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Text color="muted">
|
||||
{formatMessage(isPage ? labels.viewedPage : labels.triggeredEvent)}
|
||||
</Text>
|
||||
<Text color="muted">{formatMessage(labels.conversionRate)}</Text>
|
||||
<Text color="muted">{t(isPage ? labels.viewedPage : labels.triggeredEvent)}</Text>
|
||||
<Text color="muted">{t(labels.conversionRate)}</Text>
|
||||
</Row>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Row alignItems="center" gap>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Plus } from '@/components/icons';
|
|||
import { GoalEditForm } from './GoalEditForm';
|
||||
|
||||
export function GoalAddButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
|
|
@ -12,12 +12,12 @@ export function GoalAddButton({ websiteId }: { websiteId: string }) {
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.goal)}</Text>
|
||||
<Text>{t(labels.goal)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog
|
||||
aria-label="add goal"
|
||||
title={formatMessage(labels.goal)}
|
||||
title={t(labels.goal)}
|
||||
style={{ minWidth: 400, minHeight: 300 }}
|
||||
>
|
||||
{({ close }) => <GoalEditForm websiteId={websiteId} onClose={close} />}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export function GoalEditForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data } = useReportQuery(id);
|
||||
const { mutateAsync, error, isPending, touch } = useUpdateQuery(`/reports${id ? `/${id}` : ''}`);
|
||||
|
||||
|
|
@ -59,29 +59,19 @@ export function GoalEditForm({
|
|||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="name" label={t(labels.name)} rules={{ required: t(labels.required) }}>
|
||||
<TextField autoFocus />
|
||||
</FormField>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.action)}</Label>
|
||||
<Label>{t(labels.action)}</Label>
|
||||
<Grid columns="260px 1fr" gap>
|
||||
<Column>
|
||||
<FormField
|
||||
name="parameters.type"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="parameters.type" rules={{ required: t(labels.required) }}>
|
||||
<ActionSelect />
|
||||
</FormField>
|
||||
</Column>
|
||||
<Column>
|
||||
<FormField
|
||||
name="parameters.value"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="parameters.value" rules={{ required: t(labels.required) }}>
|
||||
{({ field }) => {
|
||||
return <LookupField websiteId={websiteId} type={type} {...field} />;
|
||||
}}
|
||||
|
|
@ -92,9 +82,9 @@ export function GoalEditForm({
|
|||
|
||||
<FormButtons>
|
||||
<Button onPress={onClose} isDisabled={isPending}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
<FormSubmitButton>{t(labels.save)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const EVENT_TYPES = {
|
|||
export function Journey({ websiteId, steps, startStep, endStep, view }: JourneyProps) {
|
||||
const [selectedNode, setSelectedNode] = useState(null);
|
||||
const [activeNode, setActiveNode] = useState(null);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data, error, isLoading } = useResultQuery<any>('journey', {
|
||||
websiteId,
|
||||
steps,
|
||||
|
|
@ -178,7 +178,7 @@ export function Journey({ websiteId, steps, startStep, endStep, view }: JourneyP
|
|||
<div className={styles.num}>{columnIndex + 1}</div>
|
||||
<div className={styles.stats}>
|
||||
<div className={styles.visitors} title={visitorCount}>
|
||||
{formatLongNumber(visitorCount)} {formatMessage(labels.visitors)}
|
||||
{formatLongNumber(visitorCount)} {t(labels.visitors)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -237,11 +237,11 @@ export function Journey({ websiteId, steps, startStep, endStep, view }: JourneyP
|
|||
</Focusable>
|
||||
<Tooltip placement="top" offset={20} showArrow>
|
||||
<Text transform="lowercase" color="red">
|
||||
{`${dropped}% ${formatMessage(labels.dropoff)}`}
|
||||
{`${dropped}% ${t(labels.dropoff)}`}
|
||||
</Text>
|
||||
<Column>
|
||||
<Text transform="lowercase">
|
||||
{`${remaining}% ${formatMessage(labels.conversion)}`}
|
||||
{`${remaining}% ${t(labels.conversion)}`}
|
||||
</Text>
|
||||
</Column>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const JOURNEY_STEPS = [2, 3, 4, 5, 6, 7];
|
|||
const DEFAULT_STEP = 3;
|
||||
|
||||
export function JourneysPage({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange();
|
||||
|
|
@ -23,15 +23,15 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
const buttons = [
|
||||
{
|
||||
id: 'all',
|
||||
label: formatMessage(labels.all),
|
||||
label: t(labels.all),
|
||||
},
|
||||
{
|
||||
id: 'views',
|
||||
label: formatMessage(labels.views),
|
||||
label: t(labels.views),
|
||||
},
|
||||
{
|
||||
id: 'events',
|
||||
label: formatMessage(labels.events),
|
||||
label: t(labels.events),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -39,12 +39,7 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
<Column gap>
|
||||
<WebsiteControls websiteId={websiteId} />
|
||||
<Grid columns="repeat(3, 1fr)" gap>
|
||||
<Select
|
||||
label={formatMessage(labels.steps)}
|
||||
value={steps}
|
||||
defaultValue={steps}
|
||||
onChange={setSteps}
|
||||
>
|
||||
<Select label={t(labels.steps)} value={steps} defaultValue={steps} onChange={setSteps}>
|
||||
{JOURNEY_STEPS.map(step => (
|
||||
<ListItem key={step} id={step}>
|
||||
{step}
|
||||
|
|
@ -53,7 +48,7 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
</Select>
|
||||
<Column>
|
||||
<SearchField
|
||||
label={formatMessage(labels.startStep)}
|
||||
label={t(labels.startStep)}
|
||||
value={startStep}
|
||||
onSearch={setStartStep}
|
||||
delay={1000}
|
||||
|
|
@ -61,7 +56,7 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
</Column>
|
||||
<Column>
|
||||
<SearchField
|
||||
label={formatMessage(labels.endStep)}
|
||||
label={t(labels.endStep)}
|
||||
value={endStep}
|
||||
onSearch={setEndStep}
|
||||
delay={1000}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export interface RetentionProps {
|
|||
}
|
||||
|
||||
export function Retention({ websiteId, days = DAYS, startDate, endDate }: RetentionProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { locale } = useLocale();
|
||||
const { data, error, isLoading } = useResultQuery('retention', {
|
||||
websiteId,
|
||||
|
|
@ -72,13 +72,13 @@ export function Retention({ websiteId, days = DAYS, startDate, endDate }: Retent
|
|||
>
|
||||
<Column>
|
||||
<Text weight="bold" align="center">
|
||||
{formatMessage(labels.cohort)}
|
||||
{t(labels.cohort)}
|
||||
</Text>
|
||||
</Column>
|
||||
{days.map(n => (
|
||||
<Column key={n}>
|
||||
<Text weight="bold" align="center" wrap="nowrap">
|
||||
{formatMessage(labels.day)} {n}
|
||||
{t(labels.day)} {n}
|
||||
</Text>
|
||||
</Column>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
setItem(CURRENCY_CONFIG, value);
|
||||
};
|
||||
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { locale, dateLocale } = useLocale();
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
const { data, error, isLoading } = useResultQuery<any>('revenue', {
|
||||
|
|
@ -48,7 +48,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
({ label: code }) => (
|
||||
<Row className={classNames(locale)} gap>
|
||||
<TypeIcon type="country" value={code} />
|
||||
<Text>{countryNames[code] || formatMessage(labels.unknown)}</Text>
|
||||
<Text>{countryNames[code] || t(labels.unknown)}</Text>
|
||||
</Row>
|
||||
),
|
||||
[countryNames, locale],
|
||||
|
|
@ -90,22 +90,22 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
return [
|
||||
{
|
||||
value: sum,
|
||||
label: formatMessage(labels.total),
|
||||
label: t(labels.total),
|
||||
formatValue: n => formatLongCurrency(n, currency),
|
||||
},
|
||||
{
|
||||
value: count ? sum / count : 0,
|
||||
label: formatMessage(labels.average),
|
||||
label: t(labels.average),
|
||||
formatValue: n => formatLongCurrency(n, currency),
|
||||
},
|
||||
{
|
||||
value: count,
|
||||
label: formatMessage(labels.transactions),
|
||||
label: t(labels.transactions),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: unique_count,
|
||||
label: formatMessage(labels.uniqueCustomers),
|
||||
label: t(labels.uniqueCustomers),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
] as any;
|
||||
|
|
@ -142,8 +142,8 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
</Panel>
|
||||
<Panel>
|
||||
<ListTable
|
||||
title={formatMessage(labels.country)}
|
||||
metric={formatMessage(labels.revenue)}
|
||||
title={t(labels.country)}
|
||||
metric={t(labels.revenue)}
|
||||
data={data?.country.map(({ name, value }: { name: string; value: number }) => ({
|
||||
label: name,
|
||||
count: Number(value),
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@ import { useMessages } from '@/components/hooks';
|
|||
import { formatLongCurrency } from '@/lib/format';
|
||||
|
||||
export function RevenueTable({ data = [] }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="currency" label={formatMessage(labels.currency)} align="end" />
|
||||
<DataColumn id="total" label={formatMessage(labels.total)} align="end">
|
||||
<DataColumn id="currency" label={t(labels.currency)} align="end" />
|
||||
<DataColumn id="total" label={t(labels.total)} align="end">
|
||||
{(row: any) => formatLongCurrency(row.sum, row.currency)}
|
||||
</DataColumn>
|
||||
<DataColumn id="average" label={formatMessage(labels.average)} align="end">
|
||||
<DataColumn id="average" label={t(labels.average)} align="end">
|
||||
{(row: any) => formatLongCurrency(row.count ? row.sum / row.count : 0, row.currency)}
|
||||
</DataColumn>
|
||||
<DataColumn id="count" label={formatMessage(labels.transactions)} align="end" />
|
||||
<DataColumn id="unique_count" label={formatMessage(labels.uniqueCustomers)} align="end" />
|
||||
<DataColumn id="count" label={t(labels.transactions)} align="end" />
|
||||
<DataColumn id="unique_count" label={t(labels.uniqueCustomers)} align="end" />
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export interface UTMProps {
|
|||
}
|
||||
|
||||
export function UTM({ websiteId, startDate, endDate }: UTMProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data, error, isLoading } = useResultQuery<any>('utm', {
|
||||
websiteId,
|
||||
startDate,
|
||||
|
|
@ -49,7 +49,7 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
|
|||
<Text transform="capitalize">{param.replace(/^utm_/, '')}</Text>
|
||||
</Heading>
|
||||
<ListTable
|
||||
metric={formatMessage(labels.views)}
|
||||
metric={t(labels.views)}
|
||||
data={items.map(({ utm, views }) => ({
|
||||
label: utm,
|
||||
count: views,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue