# 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 `` — 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 strip — nav arrows + today toggle + expandable panel | | `CalendarDaySelect` | Navigator wrapper (arrows + panel) | | `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 `` 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.