Skip to content

Placeholders

Leo edited this page Jan 26, 2024 · 18 revisions

Messages might contain embedded values, like integers in Page 1/2. These values are represented by placeholders.

Administrator Usage

Administrators use placeholders wherever they need them. Placeholders, like tags, can have attributes.

Page {page}/{pages:some:attributes}

Would be one example of how to use them. Placeholders might be number values that can be formatted. This must be specified by the plugin developers. Examples:

msg={balance:%.2f} #  --> 1.23 or 0.00

For more information see https://docs.oracle.com/javase/tutorial/java/data/numberformat.html

Choices

You might at some point want to render a plural depending on the variable content, like 'if 0 or multiple minutes remaining, render "Minutes", if 1 minute remains render "Minute"'.

In MiniMessage, this would be done with a choice placeholder, like so:

<minutes_remaining/> <minutes_remaining_choice:'Minute':'Minutes'/>

This is a rather confusing approach because we must register a choice placeholder for every other placeholder we register.

TinyTranslations and the NanoMessage syntax solves this with a choice placeholder

{minutes_remaining} {minutes_remaining ? 'Minute' : 'Minutes'} or {minutes_remaining} Minute{minutes_remaining ? '' : 's'}

We simply use the actual placeholder before the choice symbol ? (all attributes are valid) and append options after the choice symbol. If we add more than two options, the first option represents 0 occurances. The last one will always resemble all not explicitly defined occurances.

{a:b:c ? '1' : '0 or many'}
{a:b:c ? '0' : '1' : 'many'}
{a:b:c ? '0' : '1' : '2' : 'many'}
and so on

Internally when converting to basic MiniMessage, the choice placeholder becomes <choice:'<a:b:c/>':'1':'0 or many'/>

Plugin Developer Usage

To apply placeholders to your Message instance, look at the following code:

public static final Message MSG = new MessageBuilder("page").withDefault("Page <page>/<pages>").build();

// and wherever you need it:
sendMessage(MSG.formatted(
    Formatter.number("page", page),
    Formatter.number("pages", pages)
));

Messages are formatted with placeholders by calling the #formatted(TagResolver...resolvers) method. The method does not modify the original Message instance but creates a formatted Message instance instead. TagResolvers are the way to resolve Tags within a MiniMessage text, for more information, take a look at the Adventure wiki.

More examples:

MSG.formatted(
    // Replace placeholder with formatted component 
    Placeholder.component("display_name", player.getDisplayname),
    // Just render String 
    Placeholder.parsed("target", target.getName()),
    // Render number formatted or as currency
    Formatter.number("balance", playerBalance),
    // Render <friends:Friend:Friends> depending on the amount of friends.
    Formatter.choice("friends", playerFriends.size())
    // Render date <date:'yyyy-MM-dd HH:mm:ss'> as 2021-12-09 11:20:10
    Formatter.date("first_login", player.getFirstJoin())
);

Have a more detailed look at TagResolvers and the way tags are being rendered if you want to boost performance of your application. For example, if you want to add a specific element as placeholder that takes quite long to prepare or render, you would only want to render it if the placeholder is really used by administrators. You would do this by implementing a custom TagResolver instance instead of using the Placeholder class. Message Methods

The Message interface brings a series of shortcuts for the insertion of placeholders:

interface Message {
        Message insertString(String key, String value);
	Message insertString(String key, Supplier<String> value);
	Message insertComponent(String key, ComponentLike value);
	Message insertComponentLazy(String key, Supplier<ComponentLike> value);
	Message insertNumber(String key, Number value);
	Message insertNumber(String key, Supplier<Number> value);
	Message insertNumberChoice(String key, Number number);
	Message insertTemporal(String key, Temporal value);
	Message insertBool(String key, Boolean value);
	Message insertTag(String key, Tag tag);
	<E> Message insertList(String key, List<E> elements, Function<E, Message> renderer, ListSection section);
}

Methods that accept a Supplier instead of the actual type are lazy formatters, that only call the Supplier method if the Tag is actually being used. This especially makes sense if the placeholder would specifically require a database request. Why make a request if the Tag is not being rendered at all.

NanoMessage Resolvers

NanoMessage - the message format of TinyTranslations that extends the default MiniMessage - introduces some default tags and placeholders, which are listed below.

{msg:[namespace]:[key]}

This resolver resolves any message by namespace and key. Namespace is optional. See messages section for a detailled explanation.

<[style]>{slot}<[style]/>

Any style that has been registered through style storages

<repeat:[count]>{slot}</repeat>

Repeats the slot given amount of times.

<shorten:[length]>{slot}</shorten>

Shortens the slot to the given length of characters and appends "...". <shorten:4>Hello world!</shorten> will become Hell...

<shorturl:[a]:[b]>{slot}</shorturl>

Shortens an url of format

(https?://)?[^/]/(.+)

Where the "tail" is everything that comes after the domain. a describes how many characters of the tail start should be visible, with default of 3. b describes how many end characters of the tail should be visible, again default 3. so <shorturl>https://github.com/KyoriPowered/adventure</shorturl> will have a tail of KyoriPowered/adventure that is being reduced to Kyo...ure when a=3 and b=3.

<lower>{slot}</lower>

Makes the content lower case. <lower><msg:prefix></lower> will render the prefix in lower case without retyping it.

<upper>{slot}</upper>

Makes the content upper case.

<lighter>{slot}</lighter>

Makes the content brighter, where explicit colors are being used.

<brighter><red>Hello</red></brighter> is a brighter shade of red, while
<red><brighter>Hello</red></brighter> is not, since <red> is not part of the tag content.

<darker>{slot}</darker>

Makes the content darker, see <lighter>.

<reverse>{slot}</reverse>

Mirrors the slot char by char. <reverse>Hello wor<red>ld!</red></reverse> becomes the parsed version of <red>!dl</red>row olleH