Desktop D2 UI: design system, portfolio, release timeline, catalog parity, listings, health, reviews, TestFlight, checks, command palette, toasts, first-run guide, en + tr with i18n check, 7:1 theme check, dev fixture mode and screenshot harness
This commit is contained in:
parent
95b7cf7ced
commit
2ef0769730
44 changed files with 5475 additions and 742 deletions
49
apps/desktop/src/lib/format.ts
Normal file
49
apps/desktop/src/lib/format.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Locale-aware formatting for the UI (numbers, percentages, dates, "3 min ago"). Pure apart from Intl.
|
||||
import { lang, t } from '../i18n';
|
||||
|
||||
const loc = (): string => (lang() === 'tr' ? 'tr-TR' : 'en-US');
|
||||
|
||||
/** 0.0109 -> "1.09%" (Turkish: "%1,09"). */
|
||||
export function pct(x: number | null | undefined, digits = 2): string {
|
||||
if (x === null || x === undefined || Number.isNaN(x)) return '–';
|
||||
return new Intl.NumberFormat(loc(), { style: 'percent', minimumFractionDigits: digits, maximumFractionDigits: digits }).format(x);
|
||||
}
|
||||
|
||||
/** Rollout share: 0.2 -> "20%", 0.05 -> "5%". */
|
||||
export function share(x: number): string {
|
||||
return new Intl.NumberFormat(loc(), { style: 'percent', maximumFractionDigits: x < 0.1 ? 1 : 0 }).format(x);
|
||||
}
|
||||
|
||||
export const num = (n: number, digits = 0): string => new Intl.NumberFormat(loc(), { maximumFractionDigits: digits, minimumFractionDigits: digits }).format(n);
|
||||
|
||||
export function bytes(n: number | null | undefined): string {
|
||||
if (!n) return '–';
|
||||
const mb = n / 1e6;
|
||||
return mb >= 1 ? `${num(mb, mb < 10 ? 1 : 0)} MB` : `${num(n / 1e3, 0)} KB`;
|
||||
}
|
||||
|
||||
export function date(iso: string | null | undefined): string {
|
||||
if (!iso) return '–';
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return '–';
|
||||
return d.toLocaleDateString(loc(), { day: 'numeric', month: 'short', year: d.getFullYear() === new Date().getFullYear() ? undefined : 'numeric' });
|
||||
}
|
||||
|
||||
export function dateTime(iso: string | null | undefined): string {
|
||||
if (!iso) return '–';
|
||||
const d = new Date(iso);
|
||||
return Number.isNaN(d.getTime()) ? '–' : d.toLocaleString(loc(), { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
/** "just now", "4 min ago", "3 h ago", "2 d ago" (and the Turkish forms). */
|
||||
export function ago(iso: string | null | undefined, now: number = Date.now()): string {
|
||||
if (!iso) return t('time.never');
|
||||
const s = Math.round((now - new Date(iso).getTime()) / 1000);
|
||||
if (!Number.isFinite(s)) return t('time.never');
|
||||
if (s < 45) return t('time.justNow');
|
||||
const rtf = new Intl.RelativeTimeFormat(loc(), { numeric: 'auto', style: 'short' });
|
||||
if (s < 3600) return rtf.format(-Math.round(s / 60), 'minute');
|
||||
if (s < 86_400) return rtf.format(-Math.round(s / 3600), 'hour');
|
||||
if (s < 86_400 * 30) return rtf.format(-Math.round(s / 86_400), 'day');
|
||||
return date(iso);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue