The @saykit/react package provides React-specific utilities for using Saykit in both client and server components.
Installation
npm install @saykit/react saykit
Package Exports
The package provides conditional exports for client and server environments:
- Default import (
@saykit/react): Automatically resolves to client or server based on React’s export conditions
- Client import (
@saykit/react/client): Explicitly import client-only utilities
- Server import (
@saykit/react/server): Explicitly import server-only utilities
Client Components
SayProvider
Wrap your client component tree with SayProvider to provide a localized Say instance via React context.
import { SayProvider } from '@saykit/react/client';
import type { Say } from 'saykit';
function RootLayout({
locale,
messages,
children
}: {
locale: string;
messages: Say.Messages;
children: React.ReactNode;
}) {
return (
<html lang={locale}>
<body>
<SayProvider locale={locale} messages={messages}>
{children}
</SayProvider>
</body>
</html>
);
}
Props
The current locale (e.g., "en", "fr")
The messages object for the current locale
Child components that will have access to the Say instance
useSay
Access the Say instance from any client component within a SayProvider.
'use client';
import { useSay } from '@saykit/react/client';
function MyComponent() {
const say = useSay();
return <p>{say.t({ id: 'greeting' })}</p>;
}
useSay must be called within a component wrapped by SayProvider, or it will throw an error.
Say Component (Client)
Use the Say component in client components for automatic message extraction:
'use client';
import { Say } from '@saykit/react';
import { useState } from 'react';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return (
<div>
<Say>
Hello from the <strong>client</strong>
</Say>
<Say.Plural
_={count}
one="You have # item"
other="You have # items"
/>
</div>
);
}
Server Components
setSay
Set the Say instance for server components using setSay. This must be called before any getSay calls.
import { setSay } from '@saykit/react/server';
import { Say } from 'saykit';
const say = new Say({
locales: ['en', 'fr'],
messages: {
en: await import('./locales/en/messages.json'),
fr: await import('./locales/fr/messages.json'),
},
});
await say.load('en');
say.activate('en');
setSay(say);
getSay
Retrieve the current Say instance in server components:
import { getSay } from '@saykit/react/server';
export default function ServerComponent() {
const say = getSay();
return <p>{say.t({ id: 'greeting' })}</p>;
}
Say Component (Server)
The Say component works seamlessly in server components:
import { Say } from '@saykit/react';
export default function ServerComponent() {
const region = 'Server';
return (
<Say>
Hello from the <strong>{region}</strong>
</Say>
);
}
unstable_createWithSay
Create a higher-order component that automatically initializes the Say instance for server components:
import 'server-only';
import { unstable_createWithSay } from '@saykit/react/server';
import { Say } from 'saykit';
const say = new Say({
locales: ['en', 'fr'],
messages: {
en: await import('./locales/en/messages.json'),
fr: await import('./locales/fr/messages.json'),
},
});
export const withSay = unstable_createWithSay(say);
Then use it to wrap your server components:
import { withSay } from './i18n';
import type { Say } from 'saykit';
function RootLayout({
locale,
messages,
children,
}: {
locale: string;
messages: Say.Messages;
children: React.ReactNode;
}) {
return (
<html lang={locale}>
<body>
<SayProvider locale={locale} messages={messages}>
{children}
</SayProvider>
</body>
</html>
);
}
export default withSay(
RootLayout,
(props) => props.params.then(p => p.locale)
);
Say Component Features
Basic Usage
The Say component is a macro that extracts messages at build time:
import { Say } from '@saykit/react';
<Say>
Welcome to our app!
</Say>
With Variables
<Say>
Hello, <strong>{userName}</strong>!
</Say>
With React Components
<Say>
Click <Link href="/about">here</Link> to learn more
</Say>
Pluralization
<Say.Plural
_={itemCount}
_0="No items"
one="One item"
other="# items"
/>
Number props must be prefixed with _ (e.g., _0, _1, _2) to avoid JSX parser issues.
Ordinals
<Say.Ordinal
_={position}
_1="#st"
_2="#nd"
_3="#rd"
other="#th"
/>
Select (Gender/Category)
<Say.Select
_={gender}
male="He is online"
female="She is online"
other="They are online"
/>
Complete Example (Next.js App Router)
Create i18n configuration
Create src/i18n.ts for server-side Say setup:import 'server-only';
import { unstable_createWithSay } from '@saykit/react/server';
import { Say } from 'saykit';
const say = new Say({
locales: ['en', 'fr'],
messages: {
en: await import('./locales/en/messages.json').then(m => m.default),
fr: await import('./locales/fr/messages.json').then(m => m.default),
},
});
export const withSay = unstable_createWithSay(say);
export default say;
Setup root layout
Wrap your app with SayProvider in the layout:src/app/[locale]/layout.tsx
import { SayProvider } from '@saykit/react/client';
import type { Say } from 'saykit';
import say, { withSay } from '../../i18n';
export function generateStaticParams() {
return say.map(([, locale]) => ({ locale }));
}
async function RootLayout({
locale,
messages,
children,
}: {
locale: string;
messages: Say.Messages;
children: React.ReactNode;
}) {
return (
<html lang={locale}>
<body>
<SayProvider locale={locale} messages={messages}>
{children}
</SayProvider>
</body>
</html>
);
}
export default withSay(RootLayout, (p) => p.params.then(p => p.locale));
Use in server components
src/app/[locale]/server-component.tsx
import { Say } from '@saykit/react';
export default function ServerComponent() {
const region = 'Server';
return (
<Say>
Hello from the <strong>{region}</strong>
</Say>
);
}
Use in client components
src/app/[locale]/client-component.tsx
'use client';
import { Say } from '@saykit/react';
export default function ClientComponent() {
const region = 'Client';
return (
<Say>
Hello from the <strong>{region}</strong>
</Say>
);
}
Type Imports
Import types for proper TypeScript support:
import type { Say } from 'saykit';
import type { ReadonlySay } from 'saykit';
type Messages = Say.Messages;
type MessageDescriptor = Say.MessageDescriptor;
Notes
The Say component is a macro and requires the Babel plugin or unplugin to transform at build time. See Babel Plugin or Unplugin for setup instructions.
When using numeric props in JSX (like pluralization rules), prefix them with _ to avoid JSX parsing issues. For example, use _0 instead of 0.