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:
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
The source locale for your application. This must match the first locale in the locales array.
Array of supported locales. The first locale must be the source locale.locales: ['en', 'fr', 'es', 'de']
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']
}
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:
Glob patterns for files to extract messages from.include: ['src/**/*.{ts,tsx}', 'lib/**/*.js']
Glob patterns for files to exclude from extraction.exclude: ['**/*.test.ts', '**/*.spec.tsx']
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
Formatters control how messages are serialized to disk:
Location: packages/config/src/shapes.ts:18-27
File extension (without the dot).
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:
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:
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}',
},
],
});
Use a custom JSON formatter instead of PO files:
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:
saykit.config.ts
saykit.config.js
saykit.config.mjs
saykit.config.cjs
.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.