# i18n formatting (dates, times, numbers)

Use when adding or changing user-visible dates/times/numbers, RTL layout, or locale-specific pickers.

## Display formatting

- Module: `frontend/src/lib/i18n/format.ts`
- Hook: `frontend/src/lib/hooks/useAppFormatters.ts` (`useLocale()` inside)
- **Never** use raw `toLocaleDateString` / `toLocaleTimeString` / bare `Intl.DateTimeFormat(undefined, …)` in UI.

| Helper | Use for |
|--------|---------|
| `formatAppDate` | Date-only labels |
| `formatAppTime` | Time-only labels |
| `formatAppDateTime` | Combined stamp |
| `formatAppTimeRange` | Appointment ranges |
| `formatAppTableDate` | Table cells |
| `formatAppNumber` | Counts, amounts (grouped) |
| `formatAppInteger` | Calendar year/day — no comma grouping |
| `formatAppPickerDateLabel` | Calendar trigger button label |
| `APP_DATE.*` | Shared preset option objects |

## Form controls (dates & selects)

| Piece | Location / use |
|-------|----------------|
| **`AppDateInput`** | All form/filter date fields — **every locale**; masked typing + calendar popup; wire value `YYYY-MM-DD` or empty |
| **`FORM_DATE_INPUT_CLASS`** | Visual shell for date input (same padding/inset as selects; no CSS chevron) |
| **`FORM_SELECT_CLASS`** | Native `<select>` filters/fields — `ps-3 pe-10`, chevron from `globals.css` |
| **`FORM_SELECT_COMPACT_CLASS`** | Tiny selects (e.g. sort direction `↓`/`↑`) — symmetric `px-2`, no chevron gutter |
| **`Dropdown`** | Labeled form select — only where already used; prefer `FORM_SELECT_CLASS` for new filters |
| **`CompactSelect`** | Year/month/day sub-selects inside calendar panels |

**`AppDateInput` behavior**

- `fa`: Jalali display `YYYY/MM/DD` (Persian digits), parse/mask in `persianCalendar.ts`
- `en` / `nl`: Gregorian display `YYYY-MM-DD`, parse/mask in `dateInputFormat.ts`
- Calendar icon at **`end-3`** (matches select chevron inset); text uses **`text-start`** (logical — right in RTL, left in LTR)
- Popup: **`CalendarDayPartsPanel`** (shared with schedule picker); positioned via `useAnchoredPanelPosition` (fixed + viewport clamp / flip) so RTL/LTR edge fields stay on-screen
- **Do not** add native `<input type="date">` — one component for all locales

**Select chevron**

- Defined once in `frontend/src/styles/globals.css` on `.form-select:not(.form-select-no-chevron)`
- RTL: `background-position: left 0.75rem center`; LTR: `right 0.75rem center`
- `text-align: start` on selects

## Calendar / appointment pickers

| Component | Use for |
|-----------|---------|
| `ScheduleDayPicker` | Appointments / Treatment day headers — nav arrows + today toggle + expandable panel. **`compact`**: no field label; date centered; Today on the navigator row |
| `CalendarDaySelect` | Navigator wrapper (arrows + panel). Empty `label` + `showHeader={false}` puts Today beside the date (used by `compact`) |
| `CalendarDayPartsPanel` | Year / month / day row — used by schedule picker and `AppDateInput` |

Persian (`fa`): Jalali calendar + `arabext` digits via Intl (`usesPersianCalendar`). Internal model stays **`Date` at local midnight** (Gregorian) — APIs unchanged.

- Conversion: `frontend/src/lib/i18n/persianCalendar.ts`
- Gregorian typing: `frontend/src/lib/i18n/dateInputFormat.ts`
- Gregorian month labels: `schedule.monthJanuary` … message keys

## RTL

- `dir` / `lang` on `<html>` from `app/[locale]/layout.tsx`
- `isRtlLocale` in `frontend/src/i18n/routing.ts`
- Use **logical** CSS: `text-start`, `text-end`, `ps-*`, `pe-*`, `ms-*`, `me-*`
- **`Table`**: default `[&_th]:text-start [&_td]:text-start`; override with `text-center` or `text-end` on cells — **never** `text-left` / `text-right` on headers (causes header/body column drift in RTL)
- Minimal overrides in `globals.css` — avoid double-mirroring (no extra `row-reverse` on shells that already inherit `direction: rtl`)

## i18n strings

- User-facing copy: `frontend/messages/{en,fa,nl}.json` — all three locales.

## Verify

```bash
cd frontend && npx tsc --noEmit
```

Manual: switch to Persian — Cases date filters, Tasks filter row (single line + sort visible), Staff/Orgs table columns aligned; switch to English — date fields match adjacent dropdown alignment.
