Skip to main content
Saykit uses a saykit.config.ts file to define your internationalization setup. This configuration controls message extraction, locale management, and output formatting.

Configuration File

Create a saykit.config.ts in your project root:
saykit.config.ts
import { defineConfig } from '@saykit/config';

export default defineConfig({
  sourceLocale: 'en',
  locales: ['en', 'fr', 'es'],
  buckets: [
    {
      include: ['src/**/*.{ts,tsx}'],
      output: 'src/locales/{locale}/messages.{extension}',
    },
  ],
});

Configuration Schema

The configuration is type-checked using Zod schemas at runtime: Location: packages/config/src/shapes.ts:74-85

Configuration Type

sourceLocale
string
required
The source locale for your application. This must match the first locale in the locales array.
sourceLocale: 'en'
locales
string[]
required
Array of supported locales. The first locale must be the source locale.
locales: ['en', 'fr', 'es', 'de']
fallbackLocales
Record<string, string[]>
Define fallback chains for locales. If a message isn’t found in a locale, Saykit will check the fallback locales in order.
fallbackLocales: {
  'en-GB': ['en'],
  'fr-CA': ['fr', 'en'],
  'es-MX': ['es', 'en']
}
buckets
Bucket[]
required
Array of bucket configurations that define which files to extract from and where to output translations.See Bucket Configuration below.

Bucket Configuration

Buckets group related messages and control extraction and output:
include
string[]
required
Glob patterns for files to extract messages from.
include: ['src/**/*.{ts,tsx}', 'lib/**/*.js']
exclude
string[]
Glob patterns for files to exclude from extraction.
exclude: ['**/*.test.ts', '**/*.spec.tsx']
output
string
required
Output path template with {locale} and {extension} placeholders.
output: 'src/locales/{locale}/messages.{extension}'
// Generates:
// - src/locales/en/messages.po
// - src/locales/fr/messages.po
formatter
Formatter
Custom formatter for message serialization. Defaults to @saykit/format-po.See Formatter Configuration below.

Formatter Configuration

Formatters control how messages are serialized to disk: Location: packages/config/src/shapes.ts:18-27
extension
string
required
File extension (without the dot).
extension: 'po'
parse
(content: string, context: { locale: string }) => Promise<Message[]>
required
Function to parse file content into messages.
parse: async (content, { locale }) => {
  // Parse content and return Message[]
  return messages;
}
stringify
(messages: Message[], context: { locale: string }) => Promise<string>
required
Function to serialize messages to file content.
stringify: async (messages, { locale }) => {
  // Convert messages to string format
  return serialized;
}

Message Type

Messages follow this structure during extraction: Location: packages/config/src/shapes.ts:8-16
interface Message {
  message: string;        // The ICU MessageFormat string
  translation?: string;   // The translated version (if available)
  id?: string;           // Custom ID or generated hash
  context?: string;      // Disambiguation context
  comments: string[];    // Translator comments from code
  references: string[];  // Source file locations (file:line)
}

Example Configurations

Monorepo Setup

Extract messages from multiple packages:
saykit.config.ts
import { defineConfig } from '@saykit/config';

export default defineConfig({
  sourceLocale: 'en',
  locales: ['en', 'fr', 'es'],
  buckets: [
    {
      include: ['packages/ui/**/*.{ts,tsx}'],
      exclude: ['**/*.test.ts'],
      output: 'packages/ui/locales/{locale}/messages.{extension}',
    },
    {
      include: ['packages/admin/**/*.{ts,tsx}'],
      exclude: ['**/*.test.ts'],
      output: 'packages/admin/locales/{locale}/messages.{extension}',
    },
  ],
});

With Fallback Locales

Handle regional variations with fallbacks:
saykit.config.ts
import { defineConfig } from '@saykit/config';

export default defineConfig({
  sourceLocale: 'en',
  locales: ['en', 'en-GB', 'en-AU', 'fr', 'fr-CA'],
  fallbackLocales: {
    'en-GB': ['en'],
    'en-AU': ['en-GB', 'en'],
    'fr-CA': ['fr', 'en'],
  },
  buckets: [
    {
      include: ['src/**/*.{ts,tsx}'],
      output: 'locales/{locale}.{extension}',
    },
  ],
});

Custom Formatter

Use a custom JSON formatter instead of PO files:
saykit.config.ts
import { defineConfig } from '@saykit/config';

export default defineConfig({
  sourceLocale: 'en',
  locales: ['en', 'fr'],
  buckets: [
    {
      include: ['src/**/*.tsx'],
      output: 'src/i18n/{locale}.{extension}',
      formatter: {
        extension: 'json',
        parse: async (content) => {
          const data = JSON.parse(content);
          return Object.entries(data).map(([id, message]) => ({
            message: message as string,
            translation: message as string,
            id,
            comments: [],
            references: [],
          }));
        },
        stringify: async (messages) => {
          const data = Object.fromEntries(
            messages.map((m) => [
              m.id || generateHash(m.message),
              m.translation || m.message,
            ])
          );
          return JSON.stringify(data, null, 2);
        },
      },
    },
  ],
});

CLI Commands

Once configured, use these commands:
# Extract messages from source files
saykit extract
CLI Location: packages/config/src/commands/

Configuration Loading

Saykit automatically searches for configuration files:
  1. saykit.config.ts
  2. saykit.config.js
  3. saykit.config.mjs
  4. saykit.config.cjs
  5. .saykitrc.json
Location: packages/config/src/features/loader/
Use defineConfig for TypeScript autocompletion and type checking in your configuration file.
The sourceLocale must always be the first item in the locales array. This is validated at runtime.

Build docs developers (and LLMs) love