Skip to content

Commit 13d5530

Browse files
committed
Improve Persian (fa-IR) support
- Add 12 missing translation keys to fa-IR.json - Make formatNumber locale-aware via Intl.NumberFormat; abbreviated shorthand paths in formatLongNumber (1.2k, 575k, etc.) intentionally keep Latin digits and ASCII suffixes as they are universally understood - Thread locale into MetricCard and PerformanceCard default formatters; tighten formatValue prop type from any to number in both components - Set lang and dir on document.documentElement via useEffect on locale change, fixing the hardcoded lang="en" and ensuring both attributes are on the same element and applied on initial load
1 parent e347f76 commit 13d5530

5 files changed

Lines changed: 33 additions & 11 deletions

File tree

public/intl/messages/fa-IR.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"behavior": "رفتار",
3434
"block-selector": "انتخابگر بلوک",
3535
"boards": "بردها",
36+
"board-type": "نوع برد",
3637
"bounce-rate": "نرخ ریزش",
3738
"breakdown": "تفکیک",
3839
"browser": "مرورگر",
@@ -201,6 +202,7 @@
201202
"number-of-records": "{x} {x, plural, one {رکورد} other {رکورد}}",
202203
"ok": "تایید",
203204
"online": "آنلاین",
205+
"open": "ترکیبی",
204206
"organic-search": "جستجوی ارگانیک",
205207
"organic-shopping": "خرید ارگانیک",
206208
"organic-social": "شبکه اجتماعی ارگانیک",
@@ -256,6 +258,7 @@
256258
"replay": "پخش مجدد",
257259
"replay-enabled": "پخش مجدد فعال",
258260
"replay-id": "Replay ID",
261+
"replay-code": "کد پخش مجدد نشست",
259262
"replays": "پخش‌های مجدد",
260263
"reports": "گزارش‌ها",
261264
"required": "ضروری",
@@ -272,6 +275,7 @@
272275
"sample-size": "اندازه نمونه",
273276
"save": "ذخیره",
274277
"save-cohort": "ذخیره گروه",
278+
"save-replay": "ذخیره پخش مجدد",
275279
"save-segment": "ذخیره بخش",
276280
"screen": "صفحه نمایش",
277281
"screens": "صفحه",
@@ -282,6 +286,8 @@
282286
"select-component": "یک کامپوننت انتخاب کنید",
283287
"select-date": "انتخاب تاریخ",
284288
"select-filter": "انتخاب فیلتر",
289+
"select-link": "انتخاب لینک",
290+
"select-pixel": "انتخاب پیکسل",
285291
"select-role": "انتخاب نقش",
286292
"select-website": "انتخاب وب‌سایت",
287293
"session": "نشست",
@@ -338,8 +344,12 @@
338344
"unique-events": "رویدادهای یکتا",
339345
"unique-visitors": "بازدیدکننده‌های یکتا",
340346
"uniqueCustomers": "مشتریان یکتا",
347+
"customers": "مشتریان",
348+
"orders": "سفارش‌ها",
349+
"saved": "ذخیره شد",
341350
"unknown": "ناشناخته",
342351
"untitled": "بدون عنوان",
352+
"upgrade": "ارتقاء",
343353
"update": "به‌روزرسانی",
344354
"url": "URL",
345355
"user": "کاربر",
@@ -402,6 +412,7 @@
402412
"reset-website": "برای بازنشانی وب‌سایت، لطفاً {confirmation} را تایپ کنید.",
403413
"reset-website-warning": "تمامی آمارهای این وب‌سایت حذف خواهد شد اما کدهای رهگیری بدون تغییر باقی می‌ماند.",
404414
"saved": "ذخیره شد.",
415+
"select-board-entity-first": "ابتدا یک وب‌سایت، پیکسل یا لینک انتخاب کنید.",
405416
"select-component-preview": "یک کامپوننت برای پیش‌نمایش انتخاب کنید",
406417
"select-website-first": "ابتدا یک وب‌سایت انتخاب کنید",
407418
"sever-error": "خطای سرور",
@@ -415,6 +426,7 @@
415426
"transfer-website": "مالکیت وب‌سایت را به حساب خودت یا یک تیم دیگر منتقل کنید.",
416427
"triggered-event": "رویداد فعال شده",
417428
"unauthorized": "غیرمجاز",
429+
"upgrade-required": "این قابلیت نیاز به اشتراک پلن {plan} دارد.",
418430
"user-deleted": "کاربر حذف شد.",
419431
"viewed-page": "صفحه مشاهده شد",
420432
"visitor-log": "بازدیدکننده از کشور <b>{country}</b> با مروگر <b>{browser}</b> در <b>{os}</b> <b>{device}</b>"

src/components/hooks/useLocale.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ export function useLocale() {
4747
}
4848
}, [locale]);
4949

50+
useEffect(() => {
51+
document.documentElement.lang = locale.split('-')[0];
52+
document.documentElement.setAttribute('dir', getTextDirection(locale));
53+
}, [locale]);
54+
5055
useEffect(() => {
5156
const url = new URL(window?.location?.href);
5257
const locale = url.searchParams.get('locale');

src/components/metrics/MetricCard.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ReactNode } from 'react';
44
import { AnimatedDiv } from '@/components/common/AnimatedDiv';
55
import { Info } from '@/components/icons';
66
import { ChangeLabel } from '@/components/metrics/ChangeLabel';
7+
import { useLocale } from '@/components/hooks';
78
import { formatNumber } from '@/lib/format';
89

910
export interface MetricCardProps {
@@ -13,7 +14,7 @@ export interface MetricCardProps {
1314
label?: string;
1415
tooltip?: ReactNode;
1516
reverseColors?: boolean;
16-
formatValue?: (n: any) => string;
17+
formatValue?: (n: number) => string;
1718
showLabel?: boolean;
1819
showChange?: boolean;
1920
}
@@ -24,10 +25,12 @@ export const MetricCard = ({
2425
label,
2526
tooltip,
2627
reverseColors = false,
27-
formatValue = formatNumber,
28+
formatValue,
2829
showLabel = true,
2930
showChange = false,
3031
}: MetricCardProps) => {
32+
const { locale } = useLocale();
33+
const format = formatValue ?? ((n: number) => formatNumber(n, locale));
3134
const diff = value - change;
3235
const pct = diff !== 0 ? ((value - diff) / diff) * 100 : value !== 0 ? 100 : 0;
3336
const props = useSpring({ x: Number(value) || 0, from: { x: 0 } });
@@ -61,10 +64,10 @@ export const MetricCard = ({
6164
</Row>
6265
)}
6366
<Text size="4xl" weight="bold" wrap="nowrap">
64-
<AnimatedDiv title={value?.toString()}>{props?.x?.to(x => formatValue(x))}</AnimatedDiv>
67+
<AnimatedDiv title={value?.toString()}>{props?.x?.to(x => format(x))}</AnimatedDiv>
6568
</Text>
6669
{showChange && (
67-
<ChangeLabel value={change} title={formatValue(change)} reverseColors={reverseColors}>
70+
<ChangeLabel value={change} title={format(change)} reverseColors={reverseColors}>
6871
<AnimatedDiv>{changeProps?.x?.to(x => `${Math.abs(~~x)}%`)}</AnimatedDiv>
6972
</ChangeLabel>
7073
)}

src/components/metrics/PerformanceCard.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Column, Text } from '@umami/react-zen';
33
import { useRef } from 'react';
44
import { AnimatedDiv } from '@/components/common/AnimatedDiv';
55
import { Badge } from '@/components/common/Badge';
6-
import { useMessages } from '@/components/hooks';
6+
import { useLocale, useMessages } from '@/components/hooks';
77
import { WEB_VITALS_THRESHOLDS } from '@/lib/constants';
88
import { formatNumber } from '@/lib/format';
99
import styles from './PerformanceCard.module.css';
@@ -12,7 +12,7 @@ export interface PerformanceCardProps {
1212
metric: 'lcp' | 'inp' | 'cls' | 'fcp' | 'ttfb';
1313
value: number;
1414
label: string;
15-
formatValue?: (n: any) => string;
15+
formatValue?: (n: number) => string;
1616
onClick?: () => void;
1717
selected?: boolean;
1818
}
@@ -35,11 +35,13 @@ export const PerformanceCard = ({
3535
metric,
3636
value = 0,
3737
label,
38-
formatValue = formatNumber,
38+
formatValue,
3939
onClick,
4040
selected = false,
4141
}: PerformanceCardProps) => {
4242
const { t, labels } = useMessages();
43+
const { locale } = useLocale();
44+
const format = formatValue ?? ((n: number) => formatNumber(n, locale));
4345
const rating = getRating(metric, value);
4446
const prevMetricRef = useRef(metric);
4547
const metricChanged = prevMetricRef.current !== metric;
@@ -67,7 +69,7 @@ export const PerformanceCard = ({
6769
{label}
6870
</Text>
6971
<Text size="4xl" weight="bold" wrap="nowrap">
70-
<AnimatedDiv>{spring.value.to(n => formatValue(n))}</AnimatedDiv>
72+
<AnimatedDiv>{spring.value.to(n => format(n))}</AnimatedDiv>
7173
</Text>
7274
<Badge variant={RATING_VARIANTS[rating]}>
7375
{t(labels[rating === 'needs-improvement' ? 'needsImprovement' : rating])}

src/lib/format.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ export function formatShortTime(val: number, formats = ['m', 's'], space = '') {
4242
return t;
4343
}
4444

45-
export function formatNumber(n: string | number) {
46-
return Number(n).toFixed(0);
45+
export function formatNumber(n: string | number, locale = 'en-US') {
46+
return new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(Number(n));
4747
}
4848

4949
export function formatLongNumber(value: number) {
5050
const n = Number(value);
5151

5252
if (n >= 1000000000) {
53-
return `${(n / 1000000).toFixed(1)}b`;
53+
return `${(n / 1000000000).toFixed(1)}b`;
5454
}
5555
if (n >= 1000000) {
5656
return `${(n / 1000000).toFixed(1)}m`;

0 commit comments

Comments
 (0)