TypeScript, Formatting & Internationalization
How Plotcn achieves strict compile-time type safety, standards-based Intl formatting, and culturally neutral runtime internationalization without framework lock-in.
Governing principle
Plotcn treats type safety, formatting, locale behavior, and directional layout as foundational architecture rather than documentation polish added after the fact.
The governing principle is:
Strict TypeScript architecture
All public Plotcn chart APIs use strict TypeScript. any is forbidden in public prop contracts.
// ❌ Disallowed: untyped data and stringly-typed keysinterface WeakChartProps { data: any[] seriesKey: string formatter?: (val: any) => any}// ✅ Recommended: generic consumer model with inferred literal keysinterface StrictChartProps<TData> { data: readonly TData[] xKey: keyof TData series: readonly { key: NumericKeyOf<TData> label: string }[] valueFormatter?: (value: number, context: FormatterContext) => string}Generic data models
Charts are generic over the consumer's existing data model (Chart<TData>). Rather than forcing consumers to transform objects into arbitrary { x, y } structures, Plotcn reads domain properties directly:
interface ServerMetric { timestamp: string latencyMs: number p99Ms: number}// Autocompletes "timestamp", "latencyMs", and "p99Ms" directly<LineChart<ServerMetric> data={metrics} xKey="timestamp" series={[ { key: "latencyMs", label: "Average Latency" }, { key: "p99Ms", label: "99th Percentile" }, ]}/>Typed accessors
For deeply nested or computed values, charts support typed accessors alongside keys:
// Simple key pathxKey="month"// Advanced accessor pathx={(datum: ServerMetric) => new Date(datum.timestamp).getTime()}Literal series preservation
Series definitions preserve exact string literals rather than widening to string:
const series = [ { key: "revenue", label: "Revenue" }, { key: "profit", label: "Profit" },] as const satisfies readonly ChartSeries<FinancialDatum>[]// Series keys inferred as: "revenue" | "profit"Standards-based formatting
Plotcn delegates formatting to native Intl standards (Intl.NumberFormat, Intl.DateTimeFormat, Intl.RelativeTimeFormat), eliminating bloated custom localization tables.
Core formatting utilities
All formatting functions are pure, tree-shakeable, and renderer-neutral (chart-core/formatting):
| Utility | Primary function | Key options |
|---|---|---|
formatNumber() | Localized digit grouping & fractions | locale, minimumFractionDigits, maximumFractionDigits |
formatCurrency() | Currency values with explicit ISO code | currency (mandatory), locale, currencyDisplay |
formatPercentage() | Ratios & percentage points | decimals, isRatio (0.42 vs 42), locale |
formatCompactNumber() | Compact axis/badge notation | locale, maximumFractionDigits (1.2M, 4.5K) |
formatDate() | Locale-aware calendar & time formatting | dateStyle, timeStyle, timeZone, locale |
formatDuration() | Human-readable elapsed durations | Total seconds to "2h 15m" / "45s" |
Explicit currency policy
formatCurrency() strictly forbids defaulting to USD:
// Mandatory ISO currency code prevents accidental USD assumptionformatCurrency(125000, { currency: "INR", locale: "en-IN" })// ──► "₹1,25,000.00" (Indian digit grouping)formatCurrency(125000, { currency: "EUR", locale: "de-DE" })// ──► "125.000,00 €" (German punctuation & suffix symbol)Explicit percentage semantics
Never guess percentage scale based on value <= 1. Input semantics must be explicit:
// Ratio mode (0.0 to 1.0)formatPercentage(0.428, 1, true) // ──► "42.8%"// Direct percentage-point modeformatPercentage(42.8, 1, false) // ──► "42.8%"Keep raw values raw
Formatting occurs strictly at presentation boundaries (axis ticks, tooltips, data tables). Data arrays and scale domains always preserve raw numeric and date values:
Raw Semantic Data (number | Date) │ ┌────────┴────────┐ ▼ ▼Geometry Scales Formatters (on demand)(Calculations) (Ticks & Tooltips)Directionality & Internationalization (RTL)
Plotcn supports right-to-left (RTL) interfaces without distorting mathematical coordinates.
Separating UI chrome from data domains
CSS logical properties
All layout rules prioritize logical properties over physical left/right rules:
/* ❌ Physical properties */margin-left: 1rem;padding-right: 1.5rem;text-align: left;/* ✅ Logical properties */margin-inline-start: 1rem;padding-inline-end: 1.5rem;text-align: start;Unicode safety & label typography
Plotcn respects diverse writing systems:
- Arabic & Hebrew: Never letter-spaced; rendered with natural ligatures.
- Devanagari & Thai: Accommodates tall ascenders and complex diacritics in container padding.
- CJK Scripts: Supports natural ideograph wrapping without requiring space separators.
- Bidi Isolation: Mixed-direction labels (e.g., Hebrew category with USD currency) wrap with
<bdi>or directional isolation. - No Forced Uppercasing: Consumer-provided localized strings are never transformed with
text-transform: uppercase.
Summary of architectural invariants
15 Core TypeScript invariants
- Public APIs never use
any. - Consumer datum types remain generic (
Chart<TData>). - Key-based APIs infer valid keys from
TData. - Numeric fields are constrained at compile-time.
- Accessors remain strongly typed
(d: TData) => number. - Literal series keys are preserved using
as const. - Readonly arrays and configs are accepted (
readonly TData[]). - Runtime boundary inputs use
unknownbefore validation. - Public event payloads are typed with domain semantics.
- Engine-specific types remain narrow and intentional.
- Internal normalized types are never leaked publicly.
- Empty/loading arrays do not require weakening types to
any[]. - Nullability reflects actual gaps (
number | null). - Compile-time type behavior is verified in automated tests.
- Type sophistication never degrades developer ergonomics.
15 Core formatting invariants
- Formatting operates on raw semantic values.
- Native
IntlAPIs are preferred over custom tables. - USD is never globally assumed.
en-USis never globally assumed.- Percentage input semantics (ratio vs. point) are explicit.
- Dates and timestamps are validated before formatting.
- Reporting time zones can be explicitly specified.
- Compact formatting (
1.2M,45K) is locale-aware. - Axis precision and tooltip precision can differ intentionally.
- Missing values (
null,NaN) bypass normal formatters. - Currency codes are explicit and never inferred from locale.
- Formatting operations never mutate underlying data.
- Accessible screen-reader summaries share identical locale semantics.
- Formatting utilities remain renderer-neutral and pure.
- All chart engines share unified formatting contracts.
12 Core internationalization invariants
- Plotcn does not assume left-to-right UI layouts.
- CSS logical properties (
*-inline) are enforced. - UI chrome direction and data-domain direction remain distinct.
- Consumer labels are never forcibly uppercased.
- Long translated strings are supported with flexible containers.
- Unicode text renders naturally in ticks, legends, and tooltips.
- Mixed-direction text remains readable with bidirectional isolation.
- Brand names and registry IDs remain stable ASCII identifiers.
- Components integrate with application i18n frameworks (
next-intl,react-intl). - Plotcn does not ship a mandatory translation catalog.
- Server-side rendering (SSR) formatting remains deterministic.
- Localization support is verified through representative tests.