Formatting
Variables
A variable is written between two %. Its value is passed in the second argument of t():
{
"greeting": "Hello, %name%!"
}
ctx.t('greeting', {name: 'Alice'}); // "Hello, Alice!"
To write a literal %, escape it: \%. In JSON, the backslash itself has to be doubled:
{
"discount": "%value%\\% off"
}
A string with variables requires a values object, and every variable it uses must be in it. Otherwise, t() throws an error.
Expressions
An expression is written %variable:expression(parameters)%. Multiple parameters are separated by |.
if: conditional text
%variable:if(text)% outputs the text when the variable is true, nothing otherwise. The variable must be a boolean.
{
"weather": "It is %temp% degrees outside. %isHot:if(Wow, that's hot...)%"
}
ctx.t('weather', {temp: 30, isHot: true});
// "It is 30 degrees outside. Wow, that's hot..."
either: one or the other
%variable:either(if true|if false)% picks one of two texts based on a boolean. It takes exactly two parameters.
{
"status": "The raid mode is %enabled:either(on|off)%."
}
plural: plurals
%variable:plural(singular|plural|zero)% picks based on a number:
1gives the singular;0gives the third parameter when provided, the plural otherwise;- any other value gives the plural.
{
"inbox": "You have %count% %count:plural(message|messages|no messages)%"
}
ctx.t('inbox', {count: 1}); // "You have 1 message"
ctx.t('inbox', {count: 5}); // "You have 5 messages"
The variable must be a number.
date: dates
%variable:date(format)% formats a Date object. Available tokens:
YYYY: 4-digit year;YY: 2-digit year;MM: 2-digit month;DD: 2-digit day;hh: 2-digit hour;mm: 2-digit minutes;ss: 2-digit seconds.
{
"joined": "Member since %date:date(DD/MM/YYYY)%"
}
ctx.t('joined', {date: new Date(2026, 8, 24)}); // "Member since 24/09/2026"
Each token is replaced only once per format.
switch: multiple cases
%variable:switch(case:text|case:text)% returns the text of the first matching case. A case can be:
- an exact text:
%role:switch(admin:Administrator|mod:Moderator)%; - an exact number:
%level:switch(1:Beginner|2:Advanced)%; - a
min-maxrange, bounds excluded by default. Add!after a bound to include it, and use-inforinffor an open range.
{
"size": "%members:switch(-inf-50:A small server|50!-1000:A growing server|1000!-inf:A large server)%"
}
ctx.t('size', {members: 50}); // "A growing server"
If no case matches, t() throws an error: make sure your ranges cover every possible value. Don't mix text cases with a numeric value: a text case compared to a number also throws. Finally, the text of a case cannot contain a colon (:).