Internationalization
State the locale and time zone your app formats in, once — instead of inheriting whatever the runtime happened to default to.
Every Intl constructor takes a locale as its first argument and a timeZone as an option, and
both have a runtime default. Intl.DateTimeFormat() resolves to Node's en-US on a server and to
the visitor's locale in a browser; the host zone resolves to the server's zone — UTC on most
hosts — and to the visitor's.
Reading either implicitly makes what you render a property of where the code ran. On a client-only app that is invisible. Under SSR it is a hydration mismatch waiting for the first visitor configured differently from the build server.
So nothing in this kit reads them implicitly. Components resolve both through useLocale() and
useTimeZone(), and the value they get is either something you stated or a documented constant.
Thursday, September 17, 2026 at 9:30 PM
Code
<IntlProvider locale={locale} timeZone={timeZone}>
<Readout />
</IntlProvider>Quick start
Wrap the app once:
import { IntlProvider } from '@42-central/ui-react';
<IntlProvider locale="fr-FR" timeZone="Europe/Paris">
<App />
</IntlProvider>;Both props are optional and independent — setting only timeZone leaves the locale alone, and
a nested provider inherits whichever value it doesn't set, so a section can override one without
restating the other.
Any single component can still override either one for itself:
<Calendar timeZone="UTC" />Where the value comes from
If your server knows the user's locale — from their account, their session, or the
Accept-Language header — pass it down. That is the only arrangement with no compromise at all:
the server renders the right thing, the client agrees with it, and nothing changes after paint.
// app/layout.tsx
export default async function Layout({ children }: LayoutProps<'/'>) {
const locale = await resolveUserLocale();
return (
<IntlProvider locale={locale}>
<body>{children}</body>
</IntlProvider>
);
}Defaults
The two defaults are deliberately different, because the failure modes are different.
| Default | Why | |
|---|---|---|
locale | "en-US" | A wrong locale is an unfamiliar format — 09/17 instead of 17/09. Harmless, so determinism wins and detection is opt-in. |
timeZone | "auto" | A wrong zone is wrong data, not a format. A meeting at 14:00 Paris shown as 12:00 is a bug, not a preference. |
"en-US" is the same tag Ark UI defaults its own locale context to, so the
two halves of a Calendar — Ark's state machine and the kit's formatting around it — cannot
disagree.
"auto"
"auto" follows the host runtime: the browser's language, or the machine's time zone. It is
accepted anywhere a value is — on the provider, or on a single component.
<IntlProvider locale="auto" timeZone="auto">It is resolved through useSyncExternalStore with a fixed server snapshot, so it never causes a
hydration mismatch: the server and the first client render are both handed the defaults above,
and React swaps in the real values immediately after.
React doesn't guarantee that swap lands before the browser paints, and in practice it doesn't
reliably. Under SSR, "auto" can therefore show the default for one frame — an en-US date
flicking to fr-FR. That's the cost of opting in, and it's exactly why locale doesn't default
to it. A client-only app pays nothing: the server snapshot never runs there, so the first render
is already correct.
Server-side rendering
Three rules cover everything.
- State the locale if you can. A locale your server already knows, passed as a prop, is
correct on the first paint with no correction.
"auto"is the fallback for when it genuinely isn't knowable until the browser runs. - Don't read the clock during render. A component given no value anchors itself to
new Date(), which differs between the build/render and the hydration. Pass a fixednow(ordefaultValue) toSchedule,CalendarandDatePickerwhen the markup is server-rendered, the same way this site's own demos pin a fixed instant. - Prefer deterministic format options.
hour: '2-digit'is spec-guaranteed;hour: 'numeric'leaves zero-padding to each runtime's bundled ICU data, which Node and browsers don't always agree on. If you format a range,.normalize('NFKC')the result — the two ICUs disagree on which space glyph separates the endpoints.
Text direction
Direction comes from the locale. IntlProvider renders Ark's own locale provider underneath,
so an RTL locale sets dir for every Ark-backed part: Tabs, SegmentGroup, Menu, Tooltip,
HoverCard and the rest flip their layout, placement and arrow-key navigation with no extra
configuration.
<IntlProvider locale="ar-EG"><html dir="rtl"> on its own isn't enough. Without an RTL locale, Ark resolves dir="ltr" and
writes it onto each of its parts, so a Schedule inside an RTL page ends up with an RTL grid
and LTR chips and view switcher. The kit can't just let those parts inherit from the DOM: their
keyboard handling needs the direction in JavaScript, and the server can't read the DOM. In
development, the console warns once when the page and the locale disagree.
Set both from the same place, so they can't drift apart:
// app/layout.tsx
export default async function Layout({ children }: LayoutProps<'/'>) {
const { locale, dir } = await resolveUserLocale(); // e.g. { locale: 'ar-EG', dir: 'rtl' }
return (
<html lang={locale} dir={dir}>
<IntlProvider locale={locale}>
<body>{children}</body>
</IntlProvider>
</html>
);
}This is not a translation system
IntlProvider carries the inputs to Intl, and nothing else. Translated microcopy stays on each
component's own translations prop, which is deliberately prop-only with no context — see
Calendar, DatePicker or Schedule.
The split is that anything derivable from the locale is derived and never becomes a translation
key: weekday and month names, AM/PM, unit abbreviations. translations exists only for the strings
Intl can't produce.
API
IntlProvider
Prop
Type
Hooks
useLocale and useTimeZone read the
resolved values — for your own formatting, or to build a component that behaves like the kit's.
const locale = useLocale(props.locale);
const timeZone = useTimeZone(props.timeZone);Both return a concrete value, never "auto" and never undefined.
Constants
Prop
Type