Message Syntax

T18s uses the ICU MessageFormat Syntax for messages, the de-facto standard in the JavaScript ecosystem. Let’s learn how to use it.

Interpolating Values

You can interpolate strings into your messages using curly braces. The value inside the braces will be replaced with whatever value you pass when using the message.

Hello {fullName}, how are you?

You can specify that a value is not a string by adding a type-annotation in the curly braces. The known types are number, date, and time.

There are {count, number} people here
Today is {date, date}
Sarah shows up at {time, time}

You can format the value by adding a third, format options in the curly braces. You can for example specify how long the date should be.

Today is {data, date, short}
Today is {data, date, medium}
Today is {data, date, long}

These formatting options are locale aware and will do the right thing for the locale you are using. It uses the Intl API under the hood. Generally the format for interpolating values is always this.

  1. Varaible Name
  2. Format Type
  3. Format Options

The options that are available vary by type. You can find a list of all the options for each type in the ICU MessageFormat documentation. (We do support skeletons)


You can use pluralization to use different messages based on a number. This is useful for things like “1 person” vs “2 people”.

You can declare them by using the plural type and listing out the options.

{count, plural, one {There is one person here} other {There are multiple people here}}

You can also match an exact number by using an equals sign before it.

{count, plural, =0 {There is no one here} other {There is someone here}}

You can use the number itself inside the message by using the # placeholder.

{count, plural, one {There is # person here} other {There are # people here}}

You can use further interpolation inside the plural messages. Nesting is allowed.


Select allows you to use one of multiple values based on a key. This is useful for grammatical gender.

You use it by using the select type, and then listing out each option with a key and a value in curly braces.

Hello {gender, select, male {Mr.} female {Mrs.}} {name}

The special key other is used as a fallback if no other key matches.

Hello {gender, select, male {Mr.} female {Mrs.} other {Mx.}} {name}

Some i18n libraries require you to define a fallback case for all selects. T18s does not, instead relying on typesafety to ensure all values match a key.


Selectordinal are usefull for stuff like “1st” vs “2nd” vs “3rd”. You use them similarly to plural, but with the selectordinal type.

{count, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}

This is not interchangeable with plural. For example, english will use the same ordianl for 1st and 21st.

Inline HTML

Sometimes you want to dynamically insert HTML into your messages. You can do this by writing tags inside your messages. However, these tags aren’t raw html, they too are translated. When formatting the message, you will need to pass in a callback that takes in the text-content of the tag and returns the html you want to insert.

Go to our <homepageLink>Homepage</homepageLink>
$t("key", {homepageLink: (innerText) => `<a href="...">${innerText}</a>`})

When you use messages containing HTML, don't forget to use svelte's {@html} directive to render them.