@saykit/react
API reference for the React integration
@saykit/react is split across three entry points:
import { Say } from '@saykit/react'; // shared (server + client)
import { SayProvider, useSay } from '@saykit/react/client'; // client only
import { createWithSay, getSay, setSay } from '@saykit/react/server'; // server onlyThe default @saykit/react import uses the react-server export condition to swap between the server and client implementations of <Say> automatically.
@saykit/react
<Say>
The primary translation component. Author content as children.
<Say>Hello, {name}!</Say>Props:
Prop
Type
<Say> is a macro, the build-tool plugin reads the JSX, extracts the message, and rewrites the element to render say.call(...).
<Say.Plural>
<Say.Plural _={count} one="1 item" other={<>{count} items</>} />Props:
Prop
Type
<Say.Ordinal>
<Say.Ordinal _={position} _1="1st" _2="2nd" _3="3rd" other={<>{position}th</>} />Same props as <Say.Plural>, CLDR plural categories plus exact-number branches.
<Say.Select>
<Say.Select _={gender} male="He liked it" female="She liked it" other="They liked it" />Props:
Prop
Type
<Say.Number>
<Say>You have <Say.Number _={items.length} /> items</Say>
<Say.Number _={total} style="::currency/EUR" />Formats a number the way the active locale writes one. Usually a fragment inside a <Say>; on its
own it is formatted but not extracted, since there is nothing to translate.
Prop
Type
<Say.Date>
<Say>Published <Say.Date _={post.publishedAt} style="medium" /></Say>
<Say.Date _={post.publishedAt} style="::yMMMM" />Formats the date portion of a Date or timestamp.
Prop
Type
<Say.Time>
<Say>
Doors open at <Say.Time _={opensAt} style="short" />
</Say>Formats the time portion of a Date or timestamp. Same props as <Say.Date>; skeletons look like
::Hm.
@saykit/react/client
<SayProvider>
Wraps a client tree with the view its descendants resolve against. Required before any client-side use of <Say> or useSay().
It takes either a Store, or a locale and its messages. A store owns a catalogue and can switch locale; it is a live object, so it belongs to an application that holds its catalogue on the client. A locale and its messages are plain data and are what a server can send across the boundary; the provider builds a single-locale store over them, which has nothing to switch to.
<SayProvider store={store}>{children}</SayProvider>
<SayProvider locale={locale} messages={messages}>
{children}
</SayProvider>Props:
Prop
Type
Throws if given neither a store nor a locale and its messages. In a server component the props are filled in from the view established for the request, so it is written <SayProvider> with none.
useSay()
Returns the current View from the nearest <SayProvider>. Throws if no provider is in the tree.
Subscribes to the provider's store with useSyncExternalStore, so the component re-renders when the locale changes.
function Title({ name }: { name: string }) {
const say = useSay();
return <h1 title={say`Hello, ${name}!`}>{say.locale}</h1>;
}There is no hook for the store behind it. A store is a module-scope value you already hold, so switching is store.set('fr') on the one you built; a provider given a locale and its messages has no catalogue to switch through anyway.
@saykit/react/server
createWithSay()
Binds a withSay to a catalogue. withSay(Component, getLocale) wraps a server component so its view is negotiated with match(), loaded, and put into React's per-request cache() before the component renders, so a concurrent request rendering another locale reads its own.
Wrap every route segment that renders messages: a framework may render a page before the layout above it, and a parent that has not run yet has established nothing.
export const withSay = createWithSay(catalogue);
export default withSay(Page, (props) => props.params.then((params) => params.locale));Arguments:
Prop
Type
setSay()
Establishes a View for the request, for a caller that resolved one already. This is what withSay does once it has loaded the view.
setSay(await catalogue.load('fr'));One view per request: a second view for another locale takes over for everything rendered after it, including the messages <SayProvider> serialises. Development warns when it happens.
getSay()
Returns the View established for the request. Throws if nothing established one. The server counterpart of useSay().
const say = getSay();
say`Hello, ${name}!`;
say.locale;The view is held in React's per-request cache(), so a concurrent request rendering another locale reads its own.
Next
- Integration guide: usage patterns
- Vite (unplugin): the build-tool plugin
- Babel: for React Native, Expo, and other Babel pipelines