The Say class is the central runtime component of Saykit. It manages locales, message catalogs, and provides methods for loading, activating, and accessing translations.
Constructor
Creates a new Say instance with the specified configuration.
class Say <
Locale extends string = string ,
Loader extends Say . Loader < Locale > | undefined = Say . Loader < Locale > | undefined
>
constructor ( options : Say . Options < Locale , Loader >)
options
Say.Options<Locale, Loader>
required
Configuration options for the Say instance Array of supported locale codes
messages
Record<Locale, Say.Messages> | Partial<Record<Locale, Say.Messages>>
Pre-loaded messages mapped by locale. Either provide all messages upfront or use with a loader for dynamic loading.
Function that loads messages for a given locale. Required if messages are not fully provided upfront. type Loader < Locale extends string > = (
locale : Locale
) => Messages | Promise < Messages >
Example
import { Say } from 'saykit' ;
const say = new Say ({
locales: [ 'en' , 'es' , 'fr' ],
messages: {
en: { greeting: 'Hello, {name}!' },
es: { greeting: '¡Hola, {name}!' },
},
loader : async ( locale ) => {
const messages = await import ( `./locales/ ${ locale } .json` );
return messages . default ;
},
});
Properties
locale
The currently active locale.
Throws an error if no locale is currently active. Call activate() first.
messages
All available messages for the currently active locale.
get messages (): Say . Messages
A record mapping message IDs to their translated strings type Messages = { [ key : string ] : string }
Throws an error if:
No locale is currently active
No messages are loaded for the active locale
Methods
load()
Loads messages for the specified locales using the configured loader.
load ( ... locales : Locale []): this | Promise < this >
Locales to load messages for. If omitted, loads all available locales.
Returns the Say instance (or a Promise resolving to it if the loader is async)
If the loader returns a Promise, this method will also return a Promise. Otherwise, it returns synchronously.
Example
// Load specific locales
await say . load ( 'en' , 'es' );
// Load all configured locales
await say . load ();
activate()
Sets the active locale for translations.
activate ( locale : Locale ): this
The Say instance (for chaining)
Throws an error if:
The instance is frozen
No messages are loaded for the specified locale
Example
say . activate ( 'en' );
console . log ( say . locale ); // 'en'
assign()
Manually assigns messages to one or more locales.
// Assign messages to a single locale
assign ( locale : Locale , messages : Say . Messages ): this
// Bulk assign messages to multiple locales
assign ( messages : Partial < Record < Locale , Say . Messages >> ): this
The locale to assign messages to (single locale variant)
messages
Say.Messages | Partial<Record<Locale, Say.Messages>>
required
Messages to assign. Either a single messages object or a record mapping locales to messages.
The Say instance (for chaining)
Throws an error if the instance is frozen
Examples
// Assign to a single locale
say . assign ( 'en' , {
greeting: 'Hello, {name}!' ,
farewell: 'Goodbye!' ,
});
// Bulk assign to multiple locales
say . assign ({
en: { greeting: 'Hello!' },
es: { greeting: '¡Hola!' },
fr: { greeting: 'Bonjour!' },
});
clone()
Creates a deep copy of the Say instance with the same configuration and messages.
A new Say instance with the same locales, messages, and loader
Example
const original = new Say ({ locales: [ 'en' ], messages: { en: { hi: 'Hello' } } });
const copy = original . clone ();
copy . activate ( 'en' );
console . log ( copy . call ({ id: 'hi' })); // 'Hello'
freeze()
Freeze the Say instance, preventing further modifications.
freeze (): ReadonlySay < Locale , Loader >
return
ReadonlySay<Locale, Loader>
A readonly version of the Say instance that cannot be modified
The returned instance has activate(), load(), and assign() methods removed from its type signature.
Example
const say = new Say ({ /* ... */ });
const frozen = say . freeze ();
// These will throw errors:
// frozen.activate('en');
// frozen.assign('en', { ... });
map()
Maps over all locales, calling a callback with a cloned Say instance for each locale.
map < T >(
callbackfn : (
value : [ this , Locale ],
index : number ,
array : [ this , Locale ][]
) => T
): T []
Function to execute for each locale. Receives:
value: A tuple of [Say instance, locale]
index: The current index
array: The full array of tuples
Array of results from the callback function
Example
const greetings = say . map (([ instance , locale ]) => {
return instance . call ({ id: 'greeting' , name: 'World' });
});
console . log ( greetings );
// ['Hello, World!', '¡Hola, World!', 'Bonjour, World!']
reduce()
Reduces all locales to a single value by calling a callback for each locale.
reduce < T >(
callbackfn : (
previousValue : T ,
currentValue : [ this , Locale ],
currentIndex : number ,
array : [ this , Locale ][]
) => T ,
initialValue : T
): T
Function to execute for each locale. Receives:
previousValue: The accumulated value
currentValue: A tuple of [Say instance, locale]
currentIndex: The current index
array: The full array of tuples
Initial value for the accumulator
The final accumulated value
Example
const allMessages = say . reduce (( acc , [ instance , locale ]) => {
acc [ locale ] = instance . messages ;
return acc ;
}, {} as Record < string , Say . Messages >);
match()
Finds the best matching locale from a list of preferences.
match ( guesses : string []): Locale
Array of locale codes in order of preference (e.g., from Accept-Language header)
The best matching locale, or the first configured locale if no match is found
The matching algorithm:
First tries exact matches
Then tries language prefix matches (e.g., en matches en-US)
Falls back to the first configured locale
Example
const say = new Say ({ locales: [ 'en-US' , 'es-ES' , 'fr-FR' ], /* ... */ });
const locale = say . match ([ 'es-MX' , 'es' , 'en' ]);
console . log ( locale ); // 'es-ES' (prefix match)
const defaultLocale = say . match ([ 'de' , 'it' ]);
console . log ( defaultLocale ); // 'en-US' (fallback)
call()
Retrieves and formats a translation by its message ID.
call ( descriptor : { id: string ; [ match : string | number ]: unknown }): string
Object containing the message ID and any interpolation values Additional properties for interpolation (variable names and their values)
The formatted translation string
Throws an error if:
No locale is active
No messages are loaded for the active locale
The message ID is not found
The message is not a string
Example
say . activate ( 'en' );
say . assign ( 'en' , {
greeting: 'Hello, {name}!' ,
items: 'You have {count} {count, plural, one {item} other {items}}' ,
});
console . log ( say . call ({ id: 'greeting' , name: 'Alice' }));
// 'Hello, Alice!'
console . log ( say . call ({ id: 'items' , count: 5 }));
// 'You have 5 items'
Macro Methods
These methods are compile-time macros and require the Saykit Babel plugin. They will throw runtime errors if called directly without transformation.
plural()
Defines a pluralized message based on CLDR plural categories.
plural (
value : number ,
options : NumeralOptions
): string
The numeric value to determine the plural form
Plural rules keyed by CLDR categories (zero, one, two, few, many, other) or specific numbers interface NumeralOptions {
other : string ; // Required fallback
zero ?: string ;
one ?: string ;
two ?: string ;
few ?: string ;
many ?: string ;
[ digit : number ] : string ; // Exact number matches
}
Use # in option strings to represent the numeric value.
Example
say . plural ( count , {
0 : 'No items' ,
one: 'One item' ,
other: '# items' ,
})
// Transforms to: say.call({ id: '...', count })
ordinal()
Defines an ordinal message (e.g., “1st”, “2nd”, “3rd”).
ordinal (
value : number ,
options : NumeralOptions
): string
The numeric value to determine the ordinal form
Ordinal rules keyed by CLDR categories or specific numbers. Use # to represent the numeric value.
The selected ordinal form
Example
say . ordinal ( position , {
1 : '#st' ,
2 : '#nd' ,
3 : '#rd' ,
other: '#th' ,
})
// 1 → '1st', 2 → '2nd', 3 → '3rd', 4 → '4th'
select()
Defines a selection message based on a string value.
select (
value : string ,
options : SelectOptions
): string
A mapping of possible selector values to message strings interface SelectOptions {
other : string ; // Required fallback
[ match : string | number ] : string ;
}
Example
say . select ( gender , {
male: 'He' ,
female: 'She' ,
other: 'They' ,
})
// Useful for gender, status, or any categorical selection
Type Definitions
namespace Say {
type Messages = { [ key : string ] : string };
type Loader < Locale extends string > = (
locale : Locale
) => Messages | Promise < Messages >;
type Options <
Locale extends string ,
Loader extends Say . Loader < Locale > | undefined
> = {
locales : Locale [];
} & (
| { messages : Record < Locale , Messages >; loader ?: Loader }
| { messages ?: Partial < Record < Locale , Messages >>; loader : Loader }
);
}
type ReadonlySay <
Locale extends string = string ,
Loader extends Say . Loader < Locale > | undefined = Say . Loader < Locale > | undefined
> = Omit < Say < Locale , Loader >, 'activate' | 'load' | 'assign' >;