Skip to main content

Intro

Introduction to the API.

info

The Dokka documentation is available from the external Dokka link here.

I18N

The core of the API is the I18N<T> class, with T representing the type of value that a translation operation creates. You can create an I18N<T> instance using various I18N.Builder<T> implementations.

The current I18N.Builder<T> implementations by module:

  • glossa-adventure
    • MiniMessageI18N.Builder makes MiniMessageI18N : I18N<Component>

This document will cover behaviour common across all I18Ns - it will use the MiniMessageI18N.

Hello, World

  1. Create an I18N builder.
val builder = MiniMessageI18N.Builder()

You can override this default locale later, using withLocale.

  1. Register a translation
val builder = MiniMessageI18N.Builder().apply {
translation(Locale.US) {
value("hello_world", "Hello world!")
}
}
  1. Generate a translation
val i18n: I18N<Component> = builder.build(Locale.US, MiniMessage.miniMessage())

i18n.make("hello_world")
// > Hello world!

Register translations

You can register translations for different locales - these will merge on top of one another.

Locale.forLanguageTag is what you should use to create a Locale from a string: documentation here.

val english = Locale.forLanguageTag("en-US")
val german = Locale.forLanguageTag("de-DE")

val i18n = MiniMessageI18N.Builder().apply {
translation(english) {
value("greeting", "Hello!")
}

translation(german) {
value("greeting", "Hallo!")
}
}.build(english, MiniMessage.miniMessage())

// `.withLocale` returns a wrapper around the I18N with a new locale

i18n.withLocale(english).make("greeting")
// > Hello!

i18n.withLocale(german).make("greeting")
// > Hallo!

// providing no locale, we generate for the fallback `english`, or `en-US`
i18n.make("greeting")
// > Hello!

The locale passed to the builder will determine the fallback locale - which locale a message is generated for, if a key in the desired locale does not exist.

val i18n = MiniMessageI18N.Builder().apply {
translation(english) {
value("english_key", "This is only in English")
}

translation(german) {
value("german_key", "This is only in German")
}
}

i18n.withLocale(english).make("english_key")
// > This is only in English

i18n.withLocale(german).make("german_key")
// > This is only in German

i18n.withLocale(german).make("english_key")
// > This is only in English

i18n.withLocale(english).make("german_key")
// > (null)
note

The priority for locales is:

  • Current locale (create a copy of the I18N with a new current locale using .withLocale)
  • Default locale (defined on build)
  • Locale.ROOT, represented as root in configuration files

Safe calls

Note that i18n.make returns a List<T>? - nullable. If the message to be created could not be found at all, not even in the fallback locales, the function will return null.

In situations when you require a non-null value, you can use i18n.safe(...). This will guarantee a non-null value, however what this does exactly is implementation detail. For a MiniMessageI18N, it will return a list of a single component: the key passed in.

i18n.safe("some_unknown_key")
// > some_unknown_key

Single-line calls

Since i18n.make and .safe return lists, you may require a message which is not a list and is just a T or T?. Use makeOne or safeOne for these purposes. This will join the resulting list of elements into one element, done by the implementation.

val component: Component = i18n.safeOne("some_message") {
// args...
}

You can also define the T separator to use when joining the messages.

val component: Component = i18n.safeOne("some_message", i18n.newline) {
// args...
}

Translation sections

Translations are stored in a node structure, where a node can either:

  • hold children (a section node)
  • hold a value (a value node)

Sections are logical colections of translations, and are separated by the . character. They are defined in the translation DSL like so:

MiniMessageI18N.Builder().apply {
translation(english) {
section("hud") {
value("health", "Health: ...")
value("money", "Money: ...")
section("inventory") {
value("carrying", "Carrying weight: ...")
value("carry_max", "Max carrying weight: ...")
}
}
}
}

Add arguments

Glossa provides a DSL for adding arguments to i18n.make and i18n.safe calls, using the I18NArgs.Scope class. In practice:

val i18n = MiniMessageI18N.Builder().apply {
translation(english) {
value("cart_items",
"Items in cart: {items, number}",
"Last item added: <last_item>")
}
}

i18n.make("cart_items") {
// DSL scope here
icu("items", 4) // ICU argument
subst("last_item", text("Some Item")) // Substitution argument
}
Items in cart: 4
Last item added: Some Item

List arguments

Use list inside the arguments DSL to create a substitution argument for a list of Ts:

en-US: {
list_separator: ", "
authors: "Authors: <authors>
}
i18n.make("authors") {
list("authors", listOf(
text("AuthorOne"),
text("AuthorTwo")
))
}
// > Authors: AuthorOne, AuthorTwo

The special message key list_separator is placed in between each T element.

Naming convention

  • In message keys:
    • Use a-z0-9_ for individual segments
    • Use . as a separator between categories
    • Use the singular form for categories
    • Use the plural form for the leaf value if it represents multiple values, otherwise singular form
    • Examples
      • message.chat.incoming_message
      • message.chat.total_outgoing_messages
  • In argument keys:
    • Use only a-z0-9_
    • Use the singular form for a single value
    • Use the plural form for a collection or number of values
    • Examples
      • item_name
      • total_cost