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:
1 parent
80cad6ea65
commit
50edb71687
247 files changed
+1660
-2194
No files matched your search
@@ -10,132 +10,132 @@ export interface Field {
|
||||
}
|
||||
|
||||
export function useFields() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
const fields: Field[] = [
|
||||
{
|
||||
name: 'path',
|
||||
filterLabel: formatMessage(labels.path),
|
||||
label: formatMessage(labels.path),
|
||||
filterLabel: t(labels.path),
|
||||
label: t(labels.path),
|
||||
group: 'url',
|
||||
},
|
||||
{
|
||||
name: 'query',
|
||||
filterLabel: formatMessage(labels.query),
|
||||
label: formatMessage(labels.query),
|
||||
filterLabel: t(labels.query),
|
||||
label: t(labels.query),
|
||||
group: 'url',
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
filterLabel: formatMessage(labels.pageTitle),
|
||||
label: formatMessage(labels.pageTitle),
|
||||
filterLabel: t(labels.pageTitle),
|
||||
label: t(labels.pageTitle),
|
||||
group: 'url',
|
||||
},
|
||||
{
|
||||
name: 'referrer',
|
||||
filterLabel: formatMessage(labels.referrer),
|
||||
label: formatMessage(labels.referrer),
|
||||
filterLabel: t(labels.referrer),
|
||||
label: t(labels.referrer),
|
||||
group: 'sources',
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
filterLabel: formatMessage(labels.country),
|
||||
label: formatMessage(labels.country),
|
||||
filterLabel: t(labels.country),
|
||||
label: t(labels.country),
|
||||
group: 'location',
|
||||
},
|
||||
{
|
||||
name: 'region',
|
||||
filterLabel: formatMessage(labels.region),
|
||||
label: formatMessage(labels.region),
|
||||
filterLabel: t(labels.region),
|
||||
label: t(labels.region),
|
||||
group: 'location',
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
filterLabel: formatMessage(labels.city),
|
||||
label: formatMessage(labels.city),
|
||||
filterLabel: t(labels.city),
|
||||
label: t(labels.city),
|
||||
group: 'location',
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
filterLabel: formatMessage(labels.browser),
|
||||
label: formatMessage(labels.browser),
|
||||
filterLabel: t(labels.browser),
|
||||
label: t(labels.browser),
|
||||
group: 'environment',
|
||||
},
|
||||
{
|
||||
name: 'os',
|
||||
filterLabel: formatMessage(labels.os),
|
||||
label: formatMessage(labels.os),
|
||||
filterLabel: t(labels.os),
|
||||
label: t(labels.os),
|
||||
group: 'environment',
|
||||
},
|
||||
{
|
||||
name: 'device',
|
||||
filterLabel: formatMessage(labels.device),
|
||||
label: formatMessage(labels.device),
|
||||
filterLabel: t(labels.device),
|
||||
label: t(labels.device),
|
||||
group: 'environment',
|
||||
},
|
||||
{
|
||||
name: 'utmSource',
|
||||
filterLabel: formatMessage(labels.source),
|
||||
label: formatMessage(labels.utmSource),
|
||||
filterLabel: t(labels.source),
|
||||
label: t(labels.utmSource),
|
||||
group: 'utm',
|
||||
},
|
||||
{
|
||||
name: 'utmMedium',
|
||||
filterLabel: formatMessage(labels.medium),
|
||||
label: formatMessage(labels.utmMedium),
|
||||
filterLabel: t(labels.medium),
|
||||
label: t(labels.utmMedium),
|
||||
group: 'utm',
|
||||
},
|
||||
{
|
||||
name: 'utmCampaign',
|
||||
filterLabel: formatMessage(labels.campaign),
|
||||
label: formatMessage(labels.utmCampaign),
|
||||
filterLabel: t(labels.campaign),
|
||||
label: t(labels.utmCampaign),
|
||||
group: 'utm',
|
||||
},
|
||||
{
|
||||
name: 'utmContent',
|
||||
filterLabel: formatMessage(labels.content),
|
||||
label: formatMessage(labels.utmContent),
|
||||
filterLabel: t(labels.content),
|
||||
label: t(labels.utmContent),
|
||||
group: 'utm',
|
||||
},
|
||||
{
|
||||
name: 'utmTerm',
|
||||
filterLabel: formatMessage(labels.term),
|
||||
label: formatMessage(labels.utmTerm),
|
||||
filterLabel: t(labels.term),
|
||||
label: t(labels.utmTerm),
|
||||
group: 'utm',
|
||||
},
|
||||
{
|
||||
name: 'hostname',
|
||||
filterLabel: formatMessage(labels.hostname),
|
||||
label: formatMessage(labels.hostname),
|
||||
filterLabel: t(labels.hostname),
|
||||
label: t(labels.hostname),
|
||||
group: 'other',
|
||||
},
|
||||
{
|
||||
name: 'distinctId',
|
||||
filterLabel: formatMessage(labels.distinctId),
|
||||
label: formatMessage(labels.distinctId),
|
||||
filterLabel: t(labels.distinctId),
|
||||
label: t(labels.distinctId),
|
||||
group: 'other',
|
||||
},
|
||||
{
|
||||
name: 'tag',
|
||||
filterLabel: formatMessage(labels.tag),
|
||||
label: formatMessage(labels.tag),
|
||||
filterLabel: t(labels.tag),
|
||||
label: t(labels.tag),
|
||||
group: 'other',
|
||||
},
|
||||
{
|
||||
name: 'event',
|
||||
filterLabel: formatMessage(labels.event),
|
||||
label: formatMessage(labels.event),
|
||||
filterLabel: t(labels.event),
|
||||
label: t(labels.event),
|
||||
group: 'other',
|
||||
},
|
||||
];
|
||||
|
||||
const groupLabels: { key: FieldGroup; label: string }[] = [
|
||||
{ key: 'url', label: formatMessage(labels.url) },
|
||||
{ key: 'sources', label: formatMessage(labels.sources) },
|
||||
{ key: 'location', label: formatMessage(labels.location) },
|
||||
{ key: 'environment', label: formatMessage(labels.environment) },
|
||||
{ key: 'utm', label: formatMessage(labels.utm) },
|
||||
{ key: 'other', label: formatMessage(labels.other) },
|
||||
{ key: 'url', label: t(labels.url) },
|
||||
{ key: 'sources', label: t(labels.sources) },
|
||||
{ key: 'location', label: t(labels.location) },
|
||||
{ key: 'environment', label: t(labels.environment) },
|
||||
{ key: 'utm', label: t(labels.utm) },
|
||||
{ key: 'other', label: t(labels.other) },
|
||||
];
|
||||
|
||||
return { fields, groupLabels };
|
||||
|
||||
@@ -5,45 +5,45 @@ import { useMessages } from './useMessages';
|
||||
import { useNavigation } from './useNavigation';
|
||||
|
||||
export function useFilters() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { query } = useNavigation();
|
||||
const { fields } = useFields();
|
||||
|
||||
const operators = [
|
||||
{ name: 'eq', type: 'string', label: formatMessage(labels.is) },
|
||||
{ name: 'neq', type: 'string', label: formatMessage(labels.isNot) },
|
||||
{ name: 'c', type: 'string', label: formatMessage(labels.contains) },
|
||||
{ name: 'dnc', type: 'string', label: formatMessage(labels.doesNotContain) },
|
||||
{ name: 'i', type: 'array', label: formatMessage(labels.includes) },
|
||||
{ name: 'dni', type: 'array', label: formatMessage(labels.doesNotInclude) },
|
||||
{ name: 't', type: 'boolean', label: formatMessage(labels.isTrue) },
|
||||
{ name: 'f', type: 'boolean', label: formatMessage(labels.isFalse) },
|
||||
{ name: 'eq', type: 'number', label: formatMessage(labels.is) },
|
||||
{ name: 'neq', type: 'number', label: formatMessage(labels.isNot) },
|
||||
{ name: 'gt', type: 'number', label: formatMessage(labels.greaterThan) },
|
||||
{ name: 'lt', type: 'number', label: formatMessage(labels.lessThan) },
|
||||
{ name: 'gte', type: 'number', label: formatMessage(labels.greaterThanEquals) },
|
||||
{ name: 'lte', type: 'number', label: formatMessage(labels.lessThanEquals) },
|
||||
{ name: 'bf', type: 'date', label: formatMessage(labels.before) },
|
||||
{ name: 'af', type: 'date', label: formatMessage(labels.after) },
|
||||
{ name: 'eq', type: 'uuid', label: formatMessage(labels.is) },
|
||||
{ name: 'eq', type: 'string', label: t(labels.is) },
|
||||
{ name: 'neq', type: 'string', label: t(labels.isNot) },
|
||||
{ name: 'c', type: 'string', label: t(labels.contains) },
|
||||
{ name: 'dnc', type: 'string', label: t(labels.doesNotContain) },
|
||||
{ name: 'i', type: 'array', label: t(labels.includes) },
|
||||
{ name: 'dni', type: 'array', label: t(labels.doesNotInclude) },
|
||||
{ name: 't', type: 'boolean', label: t(labels.isTrue) },
|
||||
{ name: 'f', type: 'boolean', label: t(labels.isFalse) },
|
||||
{ name: 'eq', type: 'number', label: t(labels.is) },
|
||||
{ name: 'neq', type: 'number', label: t(labels.isNot) },
|
||||
{ name: 'gt', type: 'number', label: t(labels.greaterThan) },
|
||||
{ name: 'lt', type: 'number', label: t(labels.lessThan) },
|
||||
{ name: 'gte', type: 'number', label: t(labels.greaterThanEquals) },
|
||||
{ name: 'lte', type: 'number', label: t(labels.lessThanEquals) },
|
||||
{ name: 'bf', type: 'date', label: t(labels.before) },
|
||||
{ name: 'af', type: 'date', label: t(labels.after) },
|
||||
{ name: 'eq', type: 'uuid', label: t(labels.is) },
|
||||
];
|
||||
|
||||
const operatorLabels = {
|
||||
[OPERATORS.equals]: formatMessage(labels.is),
|
||||
[OPERATORS.notEquals]: formatMessage(labels.isNot),
|
||||
[OPERATORS.set]: formatMessage(labels.isSet),
|
||||
[OPERATORS.notSet]: formatMessage(labels.isNotSet),
|
||||
[OPERATORS.contains]: formatMessage(labels.contains),
|
||||
[OPERATORS.doesNotContain]: formatMessage(labels.doesNotContain),
|
||||
[OPERATORS.true]: formatMessage(labels.true),
|
||||
[OPERATORS.false]: formatMessage(labels.false),
|
||||
[OPERATORS.greaterThan]: formatMessage(labels.greaterThan),
|
||||
[OPERATORS.lessThan]: formatMessage(labels.lessThan),
|
||||
[OPERATORS.greaterThanEquals]: formatMessage(labels.greaterThanEquals),
|
||||
[OPERATORS.lessThanEquals]: formatMessage(labels.lessThanEquals),
|
||||
[OPERATORS.before]: formatMessage(labels.before),
|
||||
[OPERATORS.after]: formatMessage(labels.after),
|
||||
[OPERATORS.equals]: t(labels.is),
|
||||
[OPERATORS.notEquals]: t(labels.isNot),
|
||||
[OPERATORS.set]: t(labels.isSet),
|
||||
[OPERATORS.notSet]: t(labels.isNotSet),
|
||||
[OPERATORS.contains]: t(labels.contains),
|
||||
[OPERATORS.doesNotContain]: t(labels.doesNotContain),
|
||||
[OPERATORS.true]: t(labels.true),
|
||||
[OPERATORS.false]: t(labels.false),
|
||||
[OPERATORS.greaterThan]: t(labels.greaterThan),
|
||||
[OPERATORS.lessThan]: t(labels.lessThan),
|
||||
[OPERATORS.greaterThanEquals]: t(labels.greaterThanEquals),
|
||||
[OPERATORS.lessThanEquals]: t(labels.lessThanEquals),
|
||||
[OPERATORS.before]: t(labels.before),
|
||||
[OPERATORS.after]: t(labels.after),
|
||||
};
|
||||
|
||||
const typeFilters = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useLocale } from './useLocale';
|
||||
import { useMessages } from './useMessages';
|
||||
|
||||
export function useFormat() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { locale } = useLocale();
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
const { languageNames } = useLanguageNames(locale);
|
||||
@@ -20,7 +20,7 @@ export function useFormat() {
|
||||
};
|
||||
|
||||
const formatDevice = (value: string): string => {
|
||||
return formatMessage(labels[value] || labels.unknown);
|
||||
return t(labels[value] || labels.unknown);
|
||||
};
|
||||
|
||||
const formatCountry = (value: string): string => {
|
||||
|
||||
@@ -1,47 +1,11 @@
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { createElement, type ReactNode } from 'react';
|
||||
import { labels, type MessageDescriptor, messages } from '@/components/messages';
|
||||
import { labels, messages } from '@/components/messages';
|
||||
import type { ApiError } from '@/lib/types';
|
||||
|
||||
type FormatMessage = (
|
||||
descriptor: MessageDescriptor,
|
||||
values?: Record<string, string | number | boolean | null | undefined>,
|
||||
opts?: any,
|
||||
) => string | null;
|
||||
|
||||
interface FormattedMessageProps extends MessageDescriptor {
|
||||
values?: Record<string, ReactNode>;
|
||||
}
|
||||
|
||||
interface UseMessages {
|
||||
formatMessage: FormatMessage;
|
||||
messages: typeof messages;
|
||||
labels: typeof labels;
|
||||
getMessage: (id: string) => string;
|
||||
getErrorMessage: (error: ApiError) => string | undefined;
|
||||
FormattedMessage: (props: FormattedMessageProps) => ReactNode;
|
||||
}
|
||||
|
||||
export function useMessages(): UseMessages {
|
||||
export function useMessages() {
|
||||
const t = useTranslations();
|
||||
|
||||
const formatMessage = (
|
||||
descriptor: MessageDescriptor,
|
||||
values?: Record<string, string | number | boolean | null | undefined>,
|
||||
) => {
|
||||
if (!descriptor) return null;
|
||||
try {
|
||||
return t(descriptor.id, values);
|
||||
} catch {
|
||||
return descriptor.defaultMessage || descriptor.id;
|
||||
}
|
||||
};
|
||||
|
||||
const getMessage = (id: string) => {
|
||||
const message = Object.values(messages).find(value => value.id === `message.${id}`);
|
||||
|
||||
return message ? formatMessage(message) : id;
|
||||
};
|
||||
const getMessage = (id: string) => t(`message.${id}`);
|
||||
|
||||
const getErrorMessage = (error: ApiError) => {
|
||||
if (!error) {
|
||||
@@ -53,50 +17,5 @@ export function useMessages(): UseMessages {
|
||||
return code ? getMessage(code) : error?.message || 'Unknown error';
|
||||
};
|
||||
|
||||
function FormattedMessage({ id, defaultMessage, values }: FormattedMessageProps) {
|
||||
if (
|
||||
!values ||
|
||||
Object.values(values).every(v => typeof v === 'string' || typeof v === 'number')
|
||||
) {
|
||||
try {
|
||||
return t(id, values as any);
|
||||
} catch {
|
||||
return defaultMessage || id;
|
||||
}
|
||||
}
|
||||
|
||||
// For JSX values: get the raw ICU template and manually interpolate
|
||||
let template: string;
|
||||
try {
|
||||
template = t.raw(id) as string;
|
||||
} catch {
|
||||
template = defaultMessage || id;
|
||||
}
|
||||
|
||||
if (typeof template !== 'string') {
|
||||
return defaultMessage || id;
|
||||
}
|
||||
|
||||
// Split on {placeholder} tokens and interleave with values
|
||||
const parts: ReactNode[] = [];
|
||||
const regex = /\{(\w+)\}/g;
|
||||
let lastIndex = 0;
|
||||
|
||||
for (let match = regex.exec(template); match !== null; match = regex.exec(template)) {
|
||||
if (match.index > lastIndex) {
|
||||
parts.push(template.slice(lastIndex, match.index));
|
||||
}
|
||||
const key = match[1];
|
||||
parts.push(values[key] !== undefined ? values[key] : match[0]);
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
|
||||
if (lastIndex < template.length) {
|
||||
parts.push(template.slice(lastIndex));
|
||||
}
|
||||
|
||||
return createElement('span', null, ...parts);
|
||||
}
|
||||
|
||||
return { formatMessage, messages, labels, getMessage, getErrorMessage, FormattedMessage };
|
||||
return { t, messages, labels, getMessage, getErrorMessage };
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { useMessages } from './useMessages';
|
||||
import { useNavigation } from './useNavigation';
|
||||
|
||||
export function useWebsiteNavItems(websiteId: string) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { pathname, renderUrl } = useNavigation();
|
||||
|
||||
const renderPath = (path: string) =>
|
||||
@@ -27,110 +27,110 @@ export function useWebsiteNavItems(websiteId: string) {
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: formatMessage(labels.traffic),
|
||||
label: t(labels.traffic),
|
||||
items: [
|
||||
{
|
||||
id: 'overview',
|
||||
label: formatMessage(labels.overview),
|
||||
label: t(labels.overview),
|
||||
icon: <Eye />,
|
||||
path: renderPath(''),
|
||||
},
|
||||
{
|
||||
id: 'events',
|
||||
label: formatMessage(labels.events),
|
||||
label: t(labels.events),
|
||||
icon: <Lightning />,
|
||||
path: renderPath('/events'),
|
||||
},
|
||||
{
|
||||
id: 'sessions',
|
||||
label: formatMessage(labels.sessions),
|
||||
label: t(labels.sessions),
|
||||
icon: <User />,
|
||||
path: renderPath('/sessions'),
|
||||
},
|
||||
{
|
||||
id: 'realtime',
|
||||
label: formatMessage(labels.realtime),
|
||||
label: t(labels.realtime),
|
||||
icon: <Clock />,
|
||||
path: renderPath('/realtime'),
|
||||
},
|
||||
{
|
||||
id: 'compare',
|
||||
label: formatMessage(labels.compare),
|
||||
label: t(labels.compare),
|
||||
icon: <AlignEndHorizontal />,
|
||||
path: renderPath('/compare'),
|
||||
},
|
||||
{
|
||||
id: 'breakdown',
|
||||
label: formatMessage(labels.breakdown),
|
||||
label: t(labels.breakdown),
|
||||
icon: <Sheet />,
|
||||
path: renderPath('/breakdown'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.behavior),
|
||||
label: t(labels.behavior),
|
||||
items: [
|
||||
{
|
||||
id: 'goals',
|
||||
label: formatMessage(labels.goals),
|
||||
label: t(labels.goals),
|
||||
icon: <Target />,
|
||||
path: renderPath('/goals'),
|
||||
},
|
||||
{
|
||||
id: 'funnel',
|
||||
label: formatMessage(labels.funnels),
|
||||
label: t(labels.funnels),
|
||||
icon: <Funnel />,
|
||||
path: renderPath('/funnels'),
|
||||
},
|
||||
{
|
||||
id: 'journeys',
|
||||
label: formatMessage(labels.journeys),
|
||||
label: t(labels.journeys),
|
||||
icon: <Path />,
|
||||
path: renderPath('/journeys'),
|
||||
},
|
||||
{
|
||||
id: 'retention',
|
||||
label: formatMessage(labels.retention),
|
||||
label: t(labels.retention),
|
||||
icon: <Magnet />,
|
||||
path: renderPath('/retention'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.audience),
|
||||
label: t(labels.audience),
|
||||
items: [
|
||||
{
|
||||
id: 'segments',
|
||||
label: formatMessage(labels.segments),
|
||||
label: t(labels.segments),
|
||||
icon: <ChartPie />,
|
||||
path: renderPath('/segments'),
|
||||
},
|
||||
{
|
||||
id: 'cohorts',
|
||||
label: formatMessage(labels.cohorts),
|
||||
label: t(labels.cohorts),
|
||||
icon: <UserPlus />,
|
||||
path: renderPath('/cohorts'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.growth),
|
||||
label: t(labels.growth),
|
||||
items: [
|
||||
{
|
||||
id: 'utm',
|
||||
label: formatMessage(labels.utm),
|
||||
label: t(labels.utm),
|
||||
icon: <Tag />,
|
||||
path: renderPath('/utm'),
|
||||
},
|
||||
{
|
||||
id: 'revenue',
|
||||
label: formatMessage(labels.revenue),
|
||||
label: t(labels.revenue),
|
||||
icon: <Money />,
|
||||
path: renderPath('/revenue'),
|
||||
},
|
||||
{
|
||||
id: 'attribution',
|
||||
label: formatMessage(labels.attribution),
|
||||
label: t(labels.attribution),
|
||||
icon: <Network />,
|
||||
path: renderPath('/attribution'),
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user