Skip to content

Repository files navigation

SayKit

Compile-time i18n for JavaScript, TypeScript, React, Next.js, TanStack Start, Carbon, and more.

Coverage

SayKit is a framework-agnostic internationalisation toolkit built around compile-time message extraction, typed configuration, and small runtime primitives. You write messages inline with tagged templates or JSX, and SayKit takes care of extracting them into translation files, generating stable identifiers, and rendering them at runtime.

Warning

SayKit is under active development. APIs may change before the stable 1.0 release.

Highlights

  • Tagged-template and JSX authoring: say`Hello, ${name}!` or <Say>Hello, {name}!</Say>
  • Compile-time extraction from JS, TS, JSX, and TSX via Babel or unplugin
  • ICU MessageFormat support for plurals, ordinals, and select
  • Typed saykit.config.ts with a Zod-validated schema
  • A small core runtime, with optional framework adapters
  • Import translation files directly: no build-time replacement, no shipped extraction

Documentation

Full docs live at saykit.js.org (or run the website workspace locally). The site covers:

Packages

This is a pnpm monorepo. The published packages live in packages/*.

Package Description
saykit Core runtime: the Say class, macros, and ICU formatting
@saykit/config Config schema (defineConfig) and the saykit CLI
@saykit/react React integration: <Say>, SayProvider, server helpers
@saykit/carbon Carbon Discord-bot integration
unplugin-saykit Universal bundler plugin (Vite, Rollup, Webpack, esbuild, Rspack, …)
babel-plugin-saykit Babel plugin for SayKit
@saykit/transform-js JS/TS macro transformer (used by plugins)
@saykit/transform-jsx JSX/TSX macro transformer (used by plugins)
@saykit/format-po Gettext PO formatter
@saykit/format-json JSON formatter, with ARB and WebExtension dialects

Examples

End-to-end examples live in examples/*. Each one is a small but complete app, with a README explaining which parts of SayKit it exercises and why.

Example Stack Read it for
vanilla TypeScript + Vite The whole picture end to end — start here
react React 19 SPA + Vite SayProvider, rich-text messages, code-split catalogues
nextjs Next.js App Router + Babel Server Components, getSay, middleware locale detection
tanstack-start TanStack Start + Vite Fallback chains (en-NZ → en-GB → en), SSR locale negotiation
expo Expo / React Native + Metro The whitespace prop, device locale
carbon Carbon Discord bot on Cloudflare Workers Per-locale command registration, interaction.say / guild.say
browser-extension Chrome MV3 + Vite The webextension JSON dialect, writing into _locales/
custom-formatter Node CLI + tsdown Writing your own Formatter and Transformer

Between them they cover the plural shapes that actually catch people out — English and French (one/other), Polish (one/few/many/other), and Japanese (other only) — across Gettext PO, plain JSON, the WebExtension JSON dialect, and a hand-written YAML format.

Quick start

pnpm add saykit @saykit/config @saykit/format-po @saykit/transform-js
import { defineConfig } from '@saykit/config';
import po from '@saykit/format-po';
import js from '@saykit/transform-js';

export default defineConfig({
  locales: ['en', 'fr'],
  buckets: [
    {
      include: ['src/**/*.ts'],
      output: 'src/locales/{locale}.{extension}',
      formatter: po(),
      transformer: js(),
    },
  ],
});
import { Say } from 'saykit';
import en from './locales/en.po';
import fr from './locales/fr.po';

const say = new Say({ locales: ['en', 'fr'], messages: { en, fr } });
say.activate('en');

console.log(say`Hello, ${'world'}!`);
pnpm saykit extract

Extraction only writes the source locale (the first entry in locales); new locales get an empty placeholder file and existing translation files are left untouched, keeping diffs small and leaving translated content to your TMS. Untranslated keys fall back to a configurable fallback chain (and ultimately the source string) at load time, and saykit clean can prune orphaned and untranslated entries from other locale files when your TMS doesn't do it for you.

For framework-specific setup, see the React and Carbon integration docs.

Development

pnpm install
pnpm build      # build all packages
pnpm test       # run package tests
pnpm check      # type-check all packages
pnpm lint       # oxlint
pnpm format     # oxfmt

The repo uses Turborepo for task orchestration and Changesets for versioning and publishing.

About

Framework agnostic translation framework

Resources

Stars

Watchers

Forks

Releases

Sponsor this project

Contributors

Languages