Saykit provides compile-time macros that automatically extract translatable strings from your code and transform them into runtime translation calls. These macros require the Saykit Babel plugin to function.
Macros are not regular functions. They are transformed at compile-time by the Babel plugin and will throw runtime errors if called directly without transformation.
Template Literal Macro
say`message`
Define a translatable message using a tagged template literal.
The message string, which can include interpolated expressions
The translated message at runtime
Example
const greeting = say `Hello, ${ name } !` ;
Compile-time transformation:
// Before transformation
const greeting = say `Hello, ${ name } !` ;
// After transformation
const greeting = say . call ({
id: 'abc123' , // Auto-generated hash ID
name: name ,
});
Extracted message:
{
"abc123" : "Hello, {name}!"
}
Interpolation
You can interpolate any JavaScript expression:
say `Welcome, ${ user . name } !`
say `You have ${ count } ${ count === 1 ? 'item' : 'items' } `
say `Total: ${ price * quantity } `
The Babel plugin automatically extracts variable names and passes them to the call() method.
Descriptor Macro
say()
Provide a custom ID or context to disambiguate messages.
say ( descriptor : { id? : string ; context ?: string })
Custom identifier for the message. If omitted, a hash is generated.
Additional context to disambiguate identical strings with different meanings
Returns the say function to be called as a tagged template
Example: Custom ID
say ({ id: 'greeting.welcome' }) `Welcome back!` ;
Extracted message:
{
"greeting.welcome" : "Welcome back!"
}
Example: Context
Use context to distinguish identical strings with different meanings:
// The word "Right" in navigation
say ({ context: 'direction' }) `Right` ;
// The word "Right" meaning correct
say ({ context: 'correctness' }) `Right` ;
Extracted messages:
{
"abc123|direction" : "Right" ,
"def456|correctness" : "Right"
}
Translators can now provide different translations based on context.
Plural Macro
say.plural()
Define pluralized messages based on CLDR plural categories.
say . plural (
value : number ,
options : {
zero? : string ;
one ?: string ;
two ?: string ;
few ?: string ;
many ?: string ;
other : string ; // Required
[ digit : number ]: string ; // Exact matches
}
): string
The numeric value that determines which plural form to use
Plural forms keyed by CLDR categories or exact numbers. Use # to represent the numeric value in strings.
The appropriate plural form for the given value
Example
say . plural ( itemCount , {
0 : 'No items' ,
one: 'One item' ,
other: '# items' ,
});
Compile-time transformation:
// Before
say . plural ( itemCount , {
0 : 'No items' ,
one: 'One item' ,
other: '# items' ,
});
// After
say . call ({
id: 'xyz789' ,
itemCount: itemCount ,
});
Extracted message (ICU MessageFormat):
{
"xyz789" : "{itemCount, plural, =0 {No items} one {One item} other {# items}}"
}
CLDR Plural Categories
The available categories depend on the language:
zero : Used in Arabic and some other languages
one : Singular form (1 item)
two : Dual form (used in Arabic, Hebrew, etc.)
few : Used in Slavic languages for numbers like 2-4
many : Used in Slavic and other languages
other : Default fallback (required)
You can also use exact number matches:
say . plural ( count , {
0 : 'Nothing' ,
1 : 'Just one' ,
2 : 'A couple' ,
other: 'Several (#)' ,
});
Ordinal Macro
say.ordinal()
Define ordinal messages like “1st”, “2nd”, “3rd”.
say . ordinal (
value : number ,
options : {
one? : string ;
two ?: string ;
few ?: string ;
other : string ; // Required
[ digit : number ]: string ; // Exact matches
}
): string
The numeric value to convert to ordinal form
Ordinal forms keyed by CLDR categories or exact numbers. Use # to represent the numeric value.
The appropriate ordinal form
Example
say . ordinal ( position , {
1 : '#st' ,
2 : '#nd' ,
3 : '#rd' ,
other: '#th' ,
});
Compile-time transformation:
// Before
say . ordinal ( position , { 1 : '#st' , 2 : '#nd' , 3 : '#rd' , other: '#th' });
// After
say . call ({ id: 'ord123' , position: position });
Extracted message:
{
"ord123" : "{position, selectordinal, =1 {#st} =2 {#nd} =3 {#rd} other {#th}}"
}
Result
say . ordinal ( 1 , { ... }) // '1st'
say . ordinal ( 2 , { ... }) // '2nd'
say . ordinal ( 3 , { ... }) // '3rd'
say . ordinal ( 4 , { ... }) // '4th'
Select Macro
say.select()
Define selection-based messages for handling categories like gender or status.
say . select (
value : string ,
options : {
[match: string | number]: string ;
other : string ; // Required
}
): string
The selector value that determines which option to use
A mapping of possible selector values to message strings. Must include other as a fallback.
The message corresponding to the selector value
Example: Gender
say . select ( userGender , {
male: 'He liked your post' ,
female: 'She liked your post' ,
other: 'They liked your post' ,
});
Compile-time transformation:
// Before
say . select ( userGender , {
male: 'He liked your post' ,
female: 'She liked your post' ,
other: 'They liked your post' ,
});
// After
say . call ({
id: 'sel456' ,
userGender: userGender ,
});
Extracted message:
{
"sel456" : "{userGender, select, male {He liked your post} female {She liked your post} other {They liked your post}}"
}
Example: Status
say . select ( orderStatus , {
pending: 'Order is being processed' ,
shipped: 'Order is on its way' ,
delivered: 'Order has been delivered' ,
other: 'Unknown status' ,
});
Nesting Macros
You can nest macros for complex messages:
say ` ${ user . name } has ${ say . plural ( count , {
one: 'one unread message' ,
other: '# unread messages' ,
}) } ` ;
Extracted message:
{
"msg123" : "{userName} has {count, plural, one {one unread message} other {# unread messages}}"
}
Add comments to provide context for translators:
// translators: This appears on the user profile page
say `Edit Profile` ;
// translators: Shown when no search results are found
say `No results for " ${ query } "` ;
These comments are extracted alongside the messages and included in translation files.
Requirements
All macros require the Saykit Babel plugin to be configured in your build system.
Installation
npm install --save-dev @saykit/plugin-babel
Configuration
Add the plugin to your Babel configuration:
module . exports = {
plugins: [
[ '@saykit/plugin-babel' , {
// Plugin options
}]
]
};
See the Babel Plugin Guide for complete setup instructions.
How It Works
The Babel plugin performs the following transformations:
Parse : Identifies all say macro calls in your code
Extract : Extracts the message content and generates an ID (hash or custom)
Transform : Replaces the macro with a runtime say.call() invocation
Output : Writes extracted messages to locale files
Example Flow
Source code:
const message = say `Hello, ${ name } !` ;
After Babel transformation:
const message = say . call ({ id: 'abc123' , name });
Extracted to locale file:
{
"abc123" : "Hello, {name}!"
}
At runtime:
The say.call() method looks up abc123 in the active locale’s messages, interpolates the name variable, and returns the formatted string.